#11 feat:新增停顿行为统计数据接口

已合併
BaiQi 1 年之前 將 1 次代碼提交從 FanYanPeng/feat/behavior_record合併至 FanYanPeng/refactor

+ 13 - 0
src/main/java/com/njuzr/eaibackend/controller/BehaviorRecordController.java

@@ -4,6 +4,7 @@ import com.njuzr.eaibackend.service.BehaviorRecordService;
 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.TextAnalysisVO;
 import com.njuzr.eaibackend.vo.TextAnalysisVO;
+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.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -59,6 +60,18 @@ public class BehaviorRecordController {
         return MyResponse.success(vo);
         return MyResponse.success(vo);
     }
     }
 
 
+    /**
+     * 获取停顿行为统计数据
+     */
+    @GetMapping("/pauseStatistics")
+    public MyResponse getPauseStatistics(
+            @RequestParam Long studentId,
+            @RequestParam Long assignmentId,
+            @RequestParam Integer threshold) {
+        PauseStatisticsVO vo = behaviorOverviewService.getPauseStatistics(studentId, assignmentId, threshold);
+        return MyResponse.success(vo);
+    }
+
     @PostMapping()
     @PostMapping()
     public MyResponse addBehaviorRecord(
     public MyResponse addBehaviorRecord(
             @AuthenticationPrincipal(expression = "id") Long studentId,
             @AuthenticationPrincipal(expression = "id") Long studentId,

+ 10 - 0
src/main/java/com/njuzr/eaibackend/service/BehaviorOverviewService.java

@@ -2,6 +2,7 @@ package com.njuzr.eaibackend.service;
 
 
 import com.njuzr.eaibackend.po.BehaviorRecord;
 import com.njuzr.eaibackend.po.BehaviorRecord;
 import com.njuzr.eaibackend.vo.BehaviorOverviewVO;
 import com.njuzr.eaibackend.vo.BehaviorOverviewVO;
+import com.njuzr.eaibackend.vo.PauseStatisticsVO;
 
 
 import java.util.List;
 import java.util.List;
 
 
@@ -31,4 +32,13 @@ public interface BehaviorOverviewService {
      * 获取BehaviorOverview统计数据
      * 获取BehaviorOverview统计数据
      */
      */
     BehaviorOverviewVO getBehaviorOverview(Long studentId, Long assignmentId);
     BehaviorOverviewVO getBehaviorOverview(Long studentId, Long assignmentId);
+    
+    /**
+     * 获取停顿行为统计数据
+     * @param studentId 学生ID
+     * @param assignmentId 作业ID
+     * @param threshold 停顿阈值(ms)
+     * @return 停顿行为统计数据
+     */
+    PauseStatisticsVO getPauseStatistics(Long studentId, Long assignmentId, Integer threshold);
 }
 }

+ 120 - 0
src/main/java/com/njuzr/eaibackend/service/impl/BehaviorOverviewServiceImpl.java

@@ -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);
+    }
 }
 }

+ 31 - 0
src/main/java/com/njuzr/eaibackend/vo/PauseStatisticsVO.java

@@ -0,0 +1,31 @@
+package com.njuzr.eaibackend.vo;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+
+/**
+ * @Auther: WuZilong
+ * @Date: 2025/8/11 10:00
+ * @Description: 用于前端展示停顿行为统计数据
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class PauseStatisticsVO {
+    private Long studentId; // 学生ID
+    private Long assignmentId; // 作业ID
+    private Long totalPauseTime; // 停顿总时长(ms)
+    private Integer pauseCount; // 停顿总频次
+    private Double averagePauseTime; // 停顿平均时长(ms)
+    private Double medianPauseTime; // 停顿时长中位数(ms)
+    private Double standardDeviation; // 停顿时长标准差
+    private Integer threshold; // 使用的阈值(ms)
+    
+    /**
+     * 创建空的统计对象
+     */
+    public static PauseStatisticsVO createEmpty(Long studentId, Long assignmentId, Integer threshold) {
+        return new PauseStatisticsVO(studentId, assignmentId, 0L, 0, 0.0, 0.0, 0.0, threshold);
+    }
+}