Skip to content

Commit

Permalink
BE: [fix] User Timeline 조회 시 CheatingStatitics 수정 #6 #26
Browse files Browse the repository at this point in the history
  • Loading branch information
JongbeomLee623 committed Dec 1, 2024
1 parent 2c6c7b0 commit ea788eb
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.fortune.eyesee.dto;

import lombok.Data;
import java.util.ArrayList;
import java.util.List;

@Data
public class CheatingStatistic {
private Integer cheatingStatisticsId;
private String koreanTypeName;
private Integer cheatingCount;
private String detectedTime;

// 생성자
public CheatingStatistic(Integer cheatingStatisticsId, String koreanTypeName, Integer cheatingCount, String detectedTime) {
this.cheatingStatisticsId = cheatingStatisticsId;
this.koreanTypeName = koreanTypeName;
this.cheatingCount = cheatingCount;
this.detectedTime = detectedTime;
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.fortune.eyesee.dto;

import lombok.Data;

import java.time.LocalTime;
import java.util.List;

@Data
Expand All @@ -11,32 +13,39 @@ public class UserDetailResponseDTO {
private Integer seatNum;
private List<CheatingStatistic> cheatingStatistics;
private List<CheatingVideo> cheatingVideos;
private String examName;
private LocalTime examStartTime; // 시험 시작 시간
private Integer examDuration; // 진행 시간

public UserDetailResponseDTO(Integer userId, String userName, Integer userNum, Integer seatNum,
List<CheatingStatistic> cheatingStatistics, List<CheatingVideo> cheatingVideos) {
List<CheatingStatistic> cheatingStatistics, List<CheatingVideo> cheatingVideos
, String examName, LocalTime examStartTime, Integer examDuration) {
this.userId = userId;
this.userName = userName;
this.userNum = userNum;
this.seatNum = seatNum;
this.cheatingStatistics = cheatingStatistics;
this.cheatingVideos = cheatingVideos;
this.examName = examName;
this.examStartTime = examStartTime;
this.examDuration = examDuration;
}

@Data
public static class CheatingStatistic {
private Integer cheatingStatisticsId;
private String cheatingTypeName; // String으로 변경
private Integer cheatingCount;
private String detectedTime;

// 부정행위 통계 생성자
public CheatingStatistic(Integer cheatingStatisticsId, String cheatingTypeName, Integer cheatingCount, String detectedTime) {
this.cheatingStatisticsId = cheatingStatisticsId;
this.cheatingTypeName = cheatingTypeName;
this.cheatingCount = cheatingCount;
this.detectedTime = detectedTime;
}
}
// @Data
// public static class CheatingStatistic {
// private Integer cheatingStatisticsId;
// private String cheatingTypeName; // String으로 변경git
// private Integer cheatingCount;
// private String detectedTime;
//
// // 부정행위 통계 생성자
// public CheatingStatistic(Integer cheatingStatisticsId, String cheatingTypeName, Integer cheatingCount, String detectedTime) {
// this.cheatingStatisticsId = cheatingStatisticsId;
// this.cheatingTypeName = cheatingTypeName;
// this.cheatingCount = cheatingCount;
// this.detectedTime = detectedTime != null ? detectedTime : "N/A"; // 기본값 "N/A"
// }
// }


@Data
Expand All @@ -51,7 +60,7 @@ public CheatingVideo(Integer videoId, String startTime, String endTime, String f
this.videoId = videoId;
this.startTime = startTime;
this.endTime = endTime;
this.filepath = filepath;
this.filepath = filepath != null ? filepath : "No file available"; // 기본값
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ public class CheatingType {
private Integer cheatingTypeId;

private String cheatingTypeName; // 부정행위 종류 이름
private String koreanTypeName; // 한글 부정행위 이름
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.fortune.eyesee.enums;

public enum CheatingTypeEnum {
LOOK_AROUND("look_around", "주변 응시"),
REPEATED_GAZE("repeated_gaze", "반복된 응시"),
OBJECT("object", "부정행위 물체 감지"),
FACE_ABSENCE_LONG("face_absence_long", "장기 화면 이탈"),
FACE_ABSENCE_REPEAT("face_absence_repeat", "반복 화면 이탈"),
HAND_GESTURE("hand_gesture", "특정 손동작 반복"),
HEAD_TURN_LONG("head_turn_long", "고개 돌림 유지"),
HEAD_TURN_REPEAT("head_turn_repeat", "고개 돌림 반복");

private final String englishName;
private final String koreanName;

CheatingTypeEnum(String englishName, String koreanName) {
this.englishName = englishName;
this.koreanName = koreanName;
}

public String getEnglishName() {
return englishName;
}

public String getKoreanName() {
return koreanName;
}

public static String getKoreanNameByEnglish(String englishName) {
for (CheatingTypeEnum type : values()) {
if (type.getEnglishName().equalsIgnoreCase(englishName)) { // 대소문자 무시 비교
return type.getKoreanName();
}
}
return "알 수 없음";
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
package com.fortune.eyesee.repository;


import com.fortune.eyesee.dto.CheatingStatistic;
import com.fortune.eyesee.dto.UserDetailResponseDTO;
import com.fortune.eyesee.entity.CheatingStatistics;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface CheatingStatisticsRepository extends JpaRepository<CheatingStatistics, Integer> {
List<CheatingStatistics> findByUserId(Integer userId);
int countByUserId(Integer userId);

//int countByUserId(Integer userId);
// 특정 userId와 cheatingTypeId에 대한 통계 정보를 조회
CheatingStatistics findByUserIdAndCheatingTypeId(Integer userId, Integer cheatingTypeId);

@Query("""
SELECT new com.fortune.eyesee.dto.CheatingStatistic(
cs.cheatingStatisticsId,
ct.koreanTypeName,
cs.cheatingCount,
CAST(dc.detectedTime AS string)
)
FROM CheatingStatistics cs
JOIN CheatingType ct ON cs.cheatingTypeId = ct.cheatingTypeId
LEFT JOIN DetectedCheating dc ON cs.userId = dc.userId AND cs.cheatingTypeId = dc.cheatingTypeId
WHERE cs.userId = :userId
ORDER BY CAST(dc.detectedTime AS string) ASC
""")
List<CheatingStatistic> findStatisticsByUserId(@Param("userId") Integer userId);

// 특정 사용자 ID의 부정행위 횟수를 합산
@Query("SELECT SUM(cs.cheatingCount) FROM CheatingStatistics cs WHERE cs.userId = :userId")
Integer findTotalCheatingCountByUserId(@Param("userId") Integer userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@
import com.fortune.eyesee.common.response.BaseResponseCode;
import com.fortune.eyesee.dto.*;
import com.fortune.eyesee.entity.*;
import com.fortune.eyesee.enums.CheatingTypeEnum;
import com.fortune.eyesee.enums.ExamStatus;
import com.fortune.eyesee.repository.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ExamService {

Expand All @@ -27,6 +32,8 @@ public class ExamService {
private final CheatingTypeRepository cheatingTypeRepository;
private final AdminRepository adminRepository;



@Autowired
public ExamService(ExamRepository examRepository,
UserRepository userRepository,
Expand All @@ -46,6 +53,31 @@ public ExamService(ExamRepository examRepository,
this.adminRepository = adminRepository;
}

@Scheduled(cron = "0 0/10 * * * *") // 10분마다 실행
public void updateExamStatuses() {
LocalDate currentDate = LocalDate.now();
LocalTime currentTime = LocalTime.now();

// 필터링된 시험만 가져오기
List<Exam> activeExams = examRepository.findActiveExams(currentDate, currentTime);
LocalDateTime now = LocalDateTime.now();

for (Exam exam : activeExams) {
LocalDateTime examStartDateTime = LocalDateTime.of(exam.getExamDate(), exam.getExamStartTime());
LocalDateTime examEndDateTime = examStartDateTime.plusMinutes(exam.getExamDuration());

if (now.isBefore(examStartDateTime)) {
exam.setExamStatus(ExamStatus.BEFORE);
} else if (now.isAfter(examEndDateTime)) {
exam.setExamStatus(ExamStatus.DONE);
} else {
exam.setExamStatus(ExamStatus.IN_PROGRESS);
}
}

examRepository.saveAll(activeExams); // 상태 변경된 시험만 저장
}

// 시험 등록 메서드
public ExamResponseDTO registerExam(Integer adminId, ExamRequestDTO examRequestDTO) {
// Admin 인증 확인
Expand All @@ -71,6 +103,11 @@ public ExamResponseDTO registerExam(Integer adminId, ExamRequestDTO examRequestD
exam.setExamStatus(ExamStatus.BEFORE);
exam.setExamRandomCode(examRandomCode);

// 부정행위 유형 저장
if (examRequestDTO.getCheatingTypes() != null) {
exam.setCheatingTypes(examRequestDTO.getCheatingTypes());
}

// Exam 저장 후 ID 생성
examRepository.save(exam);

Expand Down Expand Up @@ -198,13 +235,14 @@ public UserListResponseDTO getUserListByExamId(Integer examId) {
List<User> sessionUsers = userRepository.findBySession(session);
List<UserListResponseDTO.UserInfo> sessionUserInfo = sessionUsers.stream()
.map(user -> {
int cheatingCount = cheatingStatisticsRepository.countByUserId(user.getUserId());
// 부정행위 횟수 합산
Integer cheatingCount = cheatingStatisticsRepository.findTotalCheatingCountByUserId(user.getUserId());
return new UserListResponseDTO.UserInfo(
user.getUserId(),
user.getUserName(),
user.getUserNum(),
user.getSeatNum(),
cheatingCount
cheatingCount != null ? cheatingCount : 0 // null 처리
);
})
.collect(Collectors.toList());
Expand All @@ -220,9 +258,8 @@ public UserListResponseDTO getUserListByExamId(Integer examId) {
}


// 특정 Exam ID와 User ID로 User 상세 정보 조회
public UserDetailResponseDTO getUserDetailByExamIdAndUserId(Integer examId, Integer userId) {
List<Session> sessions = sessionRepository.findByExam_ExamId(examId); // 수정된 부분
List<Session> sessions = sessionRepository.findByExam_ExamId(examId);
if (sessions.isEmpty()) {
throw new IllegalArgumentException("시험을 찾을 수 없거나 세션이 없습니다");
}
Expand All @@ -235,23 +272,27 @@ public UserDetailResponseDTO getUserDetailByExamIdAndUserId(Integer examId, Inte

User user = userOpt.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다"));

List<CheatingStatistic> cheatingStatistics = cheatingStatisticsRepository.findStatisticsByUserId(userId);

List<UserDetailResponseDTO.CheatingStatistic> cheatingStatistics = cheatingStatisticsRepository.findByUserId(userId).stream()
.map(stat -> {
String cheatingTypeName = cheatingTypeRepository.findById(stat.getCheatingTypeId())
.map(CheatingType::getCheatingTypeName)
.orElse("알 수 없음");

return new UserDetailResponseDTO.CheatingStatistic(
stat.getCheatingStatisticsId(),
cheatingTypeName,
stat.getCheatingCount(),
detectedCheatingRepository.findByUserIdAndCheatingTypeId(userId, stat.getCheatingTypeId())
.map(detected -> detected.getDetectedTime() != null ? detected.getDetectedTime().toString() : null)
.orElse(null)
);
})
.collect(Collectors.toList());
// Map<Integer, CheatingStatistic> mergedStatistics = new HashMap<>();
//
// for (CheatingStatistic stat : cheatingStatistics) {
// int statId = stat.getCheatingStatisticsId();
//
// if (mergedStatistics.containsKey(statId)) {
// CheatingStatistic existingStat = mergedStatistics.get(statId);
// existingStat.addDetectedTime(stat.getDetectedTimes().get(0));
// } else {
// mergedStatistics.put(statId, new CheatingStatistic(
// stat.getCheatingStatisticsId(),
// stat.getKoreanTypeName(),
// stat.getCheatingCount(),
// stat.getDetectedTimes().get(0)
// ));
// }
// }
//
// List<CheatingStatistic> finalStatistics = new ArrayList<>(mergedStatistics.values());

List<UserDetailResponseDTO.CheatingVideo> cheatingVideos = videoRecordingRepository.findByUserId(userId).stream()
.map(video -> new UserDetailResponseDTO.CheatingVideo(
Expand All @@ -268,7 +309,26 @@ public UserDetailResponseDTO getUserDetailByExamIdAndUserId(Integer examId, Inte
user.getUserNum(),
user.getSeatNum(),
cheatingStatistics,
cheatingVideos
cheatingVideos,
user.getSession().getExam().getExamName(),
user.getSession().getExam().getExamStartTime(),
user.getSession().getExam().getExamDuration()
);
}

public Map<String, Boolean> getCheatingTypesByExamId(Integer examId) {
Exam exam = examRepository.findById(examId)
.orElseThrow(() -> new IllegalArgumentException("Exam not found for ID: " + examId));

// JSON 컬럼에서 부정행위 타입을 가져오기
List<String> enabledCheatingTypes = exam.getCheatingTypes(); // JSON 컬럼 파싱

// 활성화된 항목만 포함하는 Map 생성
Map<String, Boolean> cheatingTypesMap = new HashMap<>();
for (String type : enabledCheatingTypes) {
cheatingTypesMap.put(type, true);
}

return cheatingTypesMap;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.fortune.eyesee.utils;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.List;

@Converter
public class CheatingTypeConverter implements AttributeConverter<List<String>, String> {
private static final ObjectMapper objectMapper = new ObjectMapper();

@Override
public String convertToDatabaseColumn(List<String> attribute) {
try {
return objectMapper.writeValueAsString(attribute);
} catch (Exception e) {
throw new IllegalArgumentException("Could not convert list to JSON string: " + attribute, e);
}
}

@Override
public List<String> convertToEntityAttribute(String dbData) {
try {
return objectMapper.readValue(dbData, new TypeReference<List<String>>() {});
} catch (Exception e) {
throw new RuntimeException("Could not convert JSON string to list", e);
}
}
}

0 comments on commit ea788eb

Please sign in to comment.