[개발 일지] 이모지, 피드백, 채팅 기록 처리

이건희·2024년 2월 16일
0

Eyeve Project

목록 보기
10/10

이번에는 마지막으로 이모지, 피드백, 채팅 기록을 처리하는 방법에 대해 작성하려고 한다. 사실 이모지, 피드백, 채팅은 시선 추적보단 단순하다.

이모지 처리

우선적으로 다른 것들도 마찬 가지지만 이모지는 한명이 이모지를 누르면 다른 방에 있는 사람 전부에게 뿌려줘야 한다. 이 부분은 방 로직에서 자신을 제외하고 WebSocket을 이용해 메세지를 전달하면 되니 생략하겠다.

사용자가 이모지를 보내면 DB에 저장한다. DB에 저장할때는 시간(수업 시작 후 진행된 시간 - Int 값)도 함께 저장해서 추후에 쉽게 통계를 내도록 구현하였다.

이모지를 처리하는 Repository와 Service는 아래와 같다.

EmojiRepository

@Repository
public interface EmojiRepository extends JpaRepository<Emoji, Long> {
    @Query("SELECT e.emojiType, e.time, COUNT(e),e.user.userId  FROM Emoji e WHERE e.room.roomName = :roomName AND e.user.userId = :userId GROUP BY e.user.userId,e.emojiType, e.time")
    List<Object[]> findEmojiCountByRoomNameAndUserId(@Param("roomName") String roomName, @Param("userId") String userId);

    @Query("SELECT e.emojiType, e.time, COUNT(e) FROM Emoji e WHERE e.room.roomName = :roomName GROUP BY e.emojiType, e.time")
    List<Object[]> findEmojiSumByRoomName(@Param("roomName") String roomName);
}

첫번째 메서드는 개인의 이모지 통계 처리를 위해 사용되고, 두번째 메서드는 전체 평균을 낼 때 사용한다.

EmojiService

@Service
@RequiredArgsConstructor
public class EmojiService {

    private final EmojiRepository emojiRepository;

    public void createAndSave(EmojiType emojiType, Rooms room, User user, int time) {
        Emoji emoji = new Emoji(emojiType, room, user, time);
        emojiRepository.save(emoji);
    }
    
	//시간별 유저의 이모지 계산
    public Map<EmojiType, List<Integer>> getUserEmojiCountByTime(String roomName, String userId) {
        List<Object[]> JPAResult = emojiRepository.findEmojiCountByRoomNameAndUserId(roomName, userId);
        return calculateEmojiCountByTime(JPAResult);
    }
    
	//시간별 전체 인원의 이모지 계산
    public Map<EmojiType, List<Integer>> getEmojiSumInRoomByTime(String roomName) {
        List<Object[]> JPAResult = emojiRepository.findEmojiSumByRoomName(roomName);
        return calculateEmojiCountByTime(JPAResult);
    }

	//이모지 계산 로직
    public Map<EmojiType, List<Integer>> calculateEmojiCountByTime(List<Object[]> JPAResult) {
    	//기본 0으로 초기화
        Map<EmojiType, List<Integer>> emojiMap = setDefaultEmojiListValue();
        
        //Repository에서 가져오는 데이터 형식은 
        //[[emojiType1, time, count], [emojiType2, time, count] etc..]와 같은 형식으로 되어 있다. 
        //(이모지가 8 종류가 있기 때문에 이모지 종류마다 배열로 가져와서 2차원 배열로 가져온다.)
        for (Object[] record : JPAResult) {
            EmojiType emojiType = (EmojiType) record[0];
            int time = (int) record[1];
            int count = ((Number) record[2]).intValue();

            List<Integer> timeList = emojiMap.get(emojiType);
            timeList.set(time, timeList.get(time) + count);
            emojiMap.put(emojiType, timeList);
        }
        return emojiMap;
    }

	//처음엔 리스트를 모두 0으로 초기화 - 이후 Count 만큼 더하기
    public Map<EmojiType, List<Integer>> setDefaultEmojiListValue() {
        Map<EmojiType, List<Integer>> emojiMap = new HashMap<>();
        Stream.of(EmojiType.values()).
                forEach(emojiType -> emojiMap.put(emojiType, new ArrayList<>(Collections.nCopies(25, 0))));
        return emojiMap;
    }
}

개인의 이모지 개산과 전체 인원의 이모지 합계 계산은 같은 메서드를 사용한다.

  1. 우선적으로 이모지 계산을 위한 배열의 값들을 모두 0으로 초기화 한다.

    • Map에 key값은 EmojiType, Value 값은 해당 EmojiType을 계산한 리스트이다.
  2. 이후 EmojiType마다 계산해서 리스트에 저장하고 반환한다.

이러한 로직을 이용해 이모지를 계산하고 통계를 낸다.


피드백 처리

피드백도 통계를 내는 것은 어렵지 않지만, 자동적으로 10분마다 시선 데이터를 판별하여 피드백을 보내야 하기 때문에 아래와 같이 설계하였다.

FeedbackInterval

public interface FeedbackInterval {

    String distinctInterval(int time);
    String getFeedbackType();

}

자동적 피드백 종류에는 2가지(양호, 위험)이 있고, 각 Interval마다 메세지가 다르므로 FeedbackInterval을 Interface로 선언하여 2가지의 피드백이 상속 받게 하였다.

GreenFeedbackInterval

public class GreenFeedbackInterval implements FeedbackInterval {
    public String distinctInterval(int time) {
        if (time == 600) {
            return GreenFeedback.INTERVAL_1.getMessage();
        } else if (time == 1200) {
            return GreenFeedback.INTERVAL_2.getMessage();
        } else if (time == 1800) {
            return GreenFeedback.INTERVAL_3.getMessage();
        } else if (time == 2400) {
            return GreenFeedback.INTERVAL_4.getMessage();
        } else {
            return GreenFeedback.INTERVAL_5.getMessage();
        }
    }

    @Override
    public String getFeedbackType() {
        return "GREEN";
    }
}

10분 기준으로 양호 학생 중에서도 메세지가 다 달라지므로 위와 같이 선언하였다.

RedFeedbackInterval

public class RedFeedbackInterval implements FeedbackInterval {
    @Override
    public String distinctInterval(int time) {
        if (time == 600) {
            return RedFeedback.INTERVAL_1.getMessage();
        } else if (time == 1200) {
            return RedFeedback.INTERVAL_2.getMessage();
        } else if (time == 1800) {
            return RedFeedback.INTERVAL_3.getMessage();
        } else if (time == 2400) {
            return RedFeedback.INTERVAL_4.getMessage();
        } else {
            return RedFeedback.INTERVAL_5.getMessage();
        }
    }

    @Override
    public String getFeedbackType() {
        return "RED";
    }
}

10분 기준으로 위험 학생 중에서도 메세지가 다 달라지므로 위와 같이 선언하였다. 해당 클래스에서 직접적으로 처리하여 이후 서비스 계층에서는 해당 클래스를 호출만 하면 값을 알 수 있게 하였다.

EyeTrackService(Feedback 부분만 발췌)

	//상대적 비율에 따른 위험, 양호 학생 판별
    public void addBadStudentByRelativeProportion(List<UserSession> sortedUserSessions, List<String> badStudents, int badStudentNum) {
        sortedUserSessions.stream()
                .limit(badStudentNum)
                .forEach(userSession -> {
                    badStudents.add(userSession.getUserId());
                    log.info("상대적 비율 나쁜 학생 추가: {}", userSession.getUserId());
                });
    }
	
    //양호, 위험 학생 처리 메서드
    public void processAtRiskStuDiscrimination(List<String> goodStudent, List<String> badStudent, Rooms room) {
        int badStudentNum = discriminateAtRiskStuNum(room.getUserInRoomList());
        List<UserSession> sortedUserSessions = sortUserSessionByConcentrateRate(room.getUserInRoomList(), room.getCreatorId());
        addBadStudentByRelativeProportion(sortedUserSessions, badStudent, badStudentNum);
        addAtRiskStudents(sortedUserSessions, goodStudent, badStudent, badStudentNum);
    }

	//위험, 양호 학생 추가 - 위험 학생만 처리하면 이외의 사람들은 자동적으로 양호 학생으로 처리
    public void addAtRiskStudents(List<UserSession> sortedUserSessions, List<String> goodStudent, List<String> badStudent, int badStudentNum) {
        sortedUserSessions.stream()
                .skip(badStudentNum)
                .forEach(userSession -> {
                    List<String> targetList = userSession.isConcentrating() ? goodStudent : badStudent;
                    targetList.add(userSession.getUserId());
                    log.info("전체 비율에 따른 학생 추가: {}에 {}", userSession.isConcentrating() ? "goodStu" : "badStu", userSession.getUserId());
                });
    }
	
    //전체 학생 수에 따른 위험 학생 수 구하는 메서드 - 상대적 비율에 따른 위험 학생 수 
    public int discriminateAtRiskStuNum(Map<String, UserSession> userInRoomList) {
        int NumOfStudents = userInRoomList.size() - 1;
        if (NumOfStudents <= 1) {
            log.info("전체 학생 수: {}, 상대적 나쁜 학생 수: {}", NumOfStudents, 0);
            return 0;
        } else if (NumOfStudents <= 3) {
            log.info("전체 학생 수: {}, 상대적 나쁜 학생 수: {}", NumOfStudents, 1);
            return 1;
        } else {
            log.info("전체 학생 수: {}, 상대적 나쁜 학생 수: {}", NumOfStudents, 2);
            return 2;
        }
    }

요구 사항에서 상대적 비율에 따른 위험 학생을 먼저 처리해야 하기 때문에 전체 학생 비율에 따라 상대적 위험 학생의 수를 먼저 구하고 이를 추가하였다. 그리고 이후 절대적 비율에 따른 위험 학생을 추가하였다.

피드백은 시선 추적과 밀접하기 때문에 통계는 시선 추적 글에서 다루었다.


채팅 처리

채팅은 사실 단순하다. 사용자가 채팅을 보내면 다른 사용자들에게 뿌려주고 DB에 저장한다. 그리고 이를 통계에 사용하면 된다.

ChatRepository

@Repository
public interface ChatRepository extends JpaRepository<Chat, Long> {
	//개인 채팅 불러오기
    @Query("SELECT c.user.userId, c.time, COUNT(c) FROM Chat c WHERE c.room.roomName = :roomName AND c.user.userId = :userId GROUP BY c.user.userId,c.time")
    List<Object[]> findChatCountByRoomNameAndUserId(@Param("roomName") String roomName, @Param("userId") String userId);
	
    //방에 있는 사람들의 채팅 합계 불러오기
    @Query("SELECT c.time, COUNT(c) FROM Chat c WHERE c.room.roomName = :roomName GROUP BY c.time")
    List<Object[]> findChatSumByRoomName(@Param("roomName") String roomName);

}

ChatService

@Service
@RequiredArgsConstructor
public class ChatService {

    private final ChatRepository chatRepository;

    public void createAndSave(Rooms room, User user, String payload, int time) {
        Chat chat = new Chat(room, user, payload, time);
        chatRepository.save(chat);
    }
    
	//개인의 채팅 처리
    public List<Integer> getUserChatCountByTime(String roomName, String userId) {
        List<Object[]> JPAResult = chatRepository.findChatCountByRoomNameAndUserId(roomName, userId);
        List<Integer> timeList = new ArrayList<>(Collections.nCopies(25, 0));
        for (Object[] record : JPAResult) {
            int time = (int) record[1];
            int count = ((Number) record[2]).intValue();
            timeList.set(time, timeList.get(time) + count);
        }
        return timeList;
    }

	//방에 있는 사람들의 채팅 합계 처리
    public List<Integer> getChatSumByTime(String roomName) {
        List<Object[]> JPAResult = chatRepository.findChatSumByRoomName(roomName);
        List<Integer> timeSumList = new ArrayList<>(Collections.nCopies(25, 0));
        for (Object[] record : JPAResult) {
            int time = (int) record[0];
            int count = ((Number) record[1]).intValue();
            timeSumList.set(time, timeSumList.get(time) + count);
        }
        return timeSumList;
    }
}

채팅도 이모지와 로직이 거의 흡사하다. 이모지와 같은 형식으로 불러오지만 채팅은 이모지처럼 8종류가 아니라서 단순하게 리스트로 처리하면 된다.

이후 합계를 구하고 평균을 구해서 프론트엔트에게 응답하면 된다.

profile
백엔드 개발자가 되겠어요

0개의 댓글