|
@@ -6,6 +6,7 @@ import com.njuzr.eaibackend.po.BehaviorRecord;
|
|
|
import com.njuzr.eaibackend.po.BehaviorOverview;
|
|
import com.njuzr.eaibackend.po.BehaviorOverview;
|
|
|
import com.njuzr.eaibackend.service.BehaviorOverviewService;
|
|
import com.njuzr.eaibackend.service.BehaviorOverviewService;
|
|
|
import com.njuzr.eaibackend.vo.BehaviorOverviewVO;
|
|
import com.njuzr.eaibackend.vo.BehaviorOverviewVO;
|
|
|
|
|
+import com.njuzr.eaibackend.vo.PauseStatisticsVO;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.scheduling.annotation.Async;
|
|
import org.springframework.scheduling.annotation.Async;
|
|
@@ -436,4 +437,123 @@ public class BehaviorOverviewServiceImpl implements BehaviorOverviewService {
|
|
|
vo.setAssignmentId(assignmentId);
|
|
vo.setAssignmentId(assignmentId);
|
|
|
return vo;
|
|
return vo;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public PauseStatisticsVO getPauseStatistics(Long studentId, Long assignmentId, Integer threshold) {
|
|
|
|
|
+ // 参数验证
|
|
|
|
|
+ if (studentId == null || assignmentId == null || threshold == null) {
|
|
|
|
|
+ log.warn("参数不能为空: studentId={}, assignmentId={}, threshold={}", studentId, assignmentId, threshold);
|
|
|
|
|
+ return PauseStatisticsVO.createEmpty(studentId, assignmentId, threshold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (threshold < 0) {
|
|
|
|
|
+ log.warn("停顿阈值不能为负数: threshold={}", threshold);
|
|
|
|
|
+ return PauseStatisticsVO.createEmpty(studentId, assignmentId, threshold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ log.info("获取停顿行为统计数据: studentId={}, assignmentId={}, threshold={}", studentId, assignmentId, threshold);
|
|
|
|
|
+
|
|
|
|
|
+ // 查询behaviorOverviews集合中的数据
|
|
|
|
|
+ Optional<BehaviorOverview> overviewOptional = behaviorOverviewMapper
|
|
|
|
|
+ .findByAssignmentIdAndStudentId(assignmentId, studentId);
|
|
|
|
|
+
|
|
|
|
|
+ if (overviewOptional.isPresent()) {
|
|
|
|
|
+ BehaviorOverview overview = overviewOptional.get();
|
|
|
|
|
+ List<BehaviorOverview.BehaviorEvent> events = overview.getEvents();
|
|
|
|
|
+
|
|
|
|
|
+ if (events != null && !events.isEmpty()) {
|
|
|
|
|
+ // 过滤出pauseTime大于阈值的事件
|
|
|
|
|
+ List<Long> pauseTimes = events.stream()
|
|
|
|
|
+ .filter(event -> event.getPauseTime() != null && event.getPauseTime() > threshold)
|
|
|
|
|
+ .map(BehaviorOverview.BehaviorEvent::getPauseTime)
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ // 计算统计数据
|
|
|
|
|
+ PauseStatisticsVO vo = calculatePauseStatistics(studentId, assignmentId, threshold, pauseTimes);
|
|
|
|
|
+ log.info("成功获取停顿行为统计数据: studentId={}, assignmentId={}, 停顿总时长={}ms, 停顿总频次={}",
|
|
|
|
|
+ studentId, assignmentId, vo.getTotalPauseTime(), vo.getPauseCount());
|
|
|
|
|
+ return vo;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ log.info("未找到BehaviorOverview数据: studentId={}, assignmentId={}", studentId, assignmentId);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("获取停顿行为统计数据失败: studentId={}, assignmentId={}, error={}",
|
|
|
|
|
+ studentId, assignmentId, e.getMessage(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 返回空的统计对象
|
|
|
|
|
+ return createEmptyPauseStatisticsVO(studentId, assignmentId, threshold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 计算停顿行为统计数据
|
|
|
|
|
+ */
|
|
|
|
|
+ private PauseStatisticsVO calculatePauseStatistics(Long studentId, Long assignmentId, Integer threshold, List<Long> pauseTimes) {
|
|
|
|
|
+ if (pauseTimes == null || pauseTimes.isEmpty()) {
|
|
|
|
|
+ return PauseStatisticsVO.createEmpty(studentId, assignmentId, threshold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算总时长
|
|
|
|
|
+ Long totalPauseTime = pauseTimes.stream().mapToLong(Long::longValue).sum();
|
|
|
|
|
+
|
|
|
|
|
+ // 计算总频次
|
|
|
|
|
+ int pauseCount = pauseTimes.size();
|
|
|
|
|
+
|
|
|
|
|
+ // 计算平均时长
|
|
|
|
|
+ double averagePauseTime = totalPauseTime / (double) pauseCount;
|
|
|
|
|
+
|
|
|
|
|
+ // 计算中位数
|
|
|
|
|
+ double medianPauseTime = calculateMedian(pauseTimes);
|
|
|
|
|
+
|
|
|
|
|
+ // 计算标准差
|
|
|
|
|
+ double standardDeviation = calculateStandardDeviation(pauseTimes, averagePauseTime);
|
|
|
|
|
+
|
|
|
|
|
+ // 创建并返回结果对象
|
|
|
|
|
+ return new PauseStatisticsVO(studentId, assignmentId, totalPauseTime, pauseCount,
|
|
|
|
|
+ averagePauseTime, medianPauseTime, standardDeviation, threshold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 计算中位数
|
|
|
|
|
+ */
|
|
|
|
|
+ private double calculateMedian(List<Long> numbers) {
|
|
|
|
|
+ // 排序
|
|
|
|
|
+ List<Long> sortedNumbers = numbers.stream().sorted().collect(Collectors.toList());
|
|
|
|
|
+ int size = sortedNumbers.size();
|
|
|
|
|
+
|
|
|
|
|
+ if (size % 2 == 0) {
|
|
|
|
|
+ // 偶数个元素,取中间两个的平均值
|
|
|
|
|
+ return (sortedNumbers.get(size / 2 - 1) + sortedNumbers.get(size / 2)) / 2.0;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 奇数个元素,取中间的那个
|
|
|
|
|
+ return sortedNumbers.get(size / 2);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 计算标准差
|
|
|
|
|
+ */
|
|
|
|
|
+ private double calculateStandardDeviation(List<Long> numbers, double mean) {
|
|
|
|
|
+ if (numbers.size() <= 1) {
|
|
|
|
|
+ return 0.0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算方差
|
|
|
|
|
+ double variance = numbers.stream()
|
|
|
|
|
+ .mapToDouble(num -> Math.pow(num - mean, 2))
|
|
|
|
|
+ .average()
|
|
|
|
|
+ .orElse(0.0);
|
|
|
|
|
+
|
|
|
|
|
+ // 标准差是方差的平方根
|
|
|
|
|
+ return Math.sqrt(variance);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 创建空的PauseStatisticsVO对象
|
|
|
|
|
+ */
|
|
|
|
|
+ private PauseStatisticsVO createEmptyPauseStatisticsVO(Long studentId, Long assignmentId, Integer threshold) {
|
|
|
|
|
+ return PauseStatisticsVO.createEmpty(studentId, assignmentId, threshold);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|