#5 fix:返回的行为记录修改为热度图需要的字段和格式

Zlúčené
BaiQi mergnuté 1 commitov z FanYanPeng/feat/behavior_record do FanYanPeng/refactor 1 rok pred

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

@@ -30,6 +30,13 @@ public class BehaviorRecordController {
         return MyResponse.success(behaviorRecordService.find(assignmentId, studentId));
         return MyResponse.success(behaviorRecordService.find(assignmentId, studentId));
     }
     }
 
 
+    @GetMapping("/all")
+    public MyResponse getBehaviorRecordALL(
+            @RequestParam Long studentId,
+            @RequestParam Long assignmentId) {
+        return MyResponse.success(behaviorRecordService.findALL(assignmentId, studentId));
+    }
+
     @PostMapping()
     @PostMapping()
     public MyResponse addBehaviorRecord(
     public MyResponse addBehaviorRecord(
             @AuthenticationPrincipal(expression = "id") Long studentId,
             @AuthenticationPrincipal(expression = "id") Long studentId,

+ 1 - 1
src/main/java/com/njuzr/eaibackend/po/BehaviorRecord.java

@@ -17,7 +17,7 @@ import java.util.List;
         // 复合索引:assignment_id + student_id + created_at (降序)
         // 复合索引:assignment_id + student_id + created_at (降序)
         @CompoundIndex(
         @CompoundIndex(
                 name = "assignment_student_created_idx",
                 name = "assignment_student_created_idx",
-                def = "{'assignment_id': 1, 'student_id': 1, 'created_at': -1}",
+                def = "{'assignment_id': 1, 'student_id': 1, 'created_at': 1}",
                 background = true    // 后台构建,避免阻塞操作
                 background = true    // 后台构建,避免阻塞操作
         )
         )
 })
 })

+ 4 - 0
src/main/java/com/njuzr/eaibackend/service/BehaviorRecordService.java

@@ -1,6 +1,8 @@
 package com.njuzr.eaibackend.service;
 package com.njuzr.eaibackend.service;
 
 
 import com.njuzr.eaibackend.po.BehaviorRecord;
 import com.njuzr.eaibackend.po.BehaviorRecord;
+import com.njuzr.eaibackend.vo.BehaviorRecordVO;
+import com.njuzr.eaibackend.vo.TextAnalysisVO;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartFile;
 
 
 import java.util.List;
 import java.util.List;
@@ -12,4 +14,6 @@ import java.util.List;
 public interface BehaviorRecordService {
 public interface BehaviorRecordService {
     List<BehaviorRecord> find(Long assignmentId, Long studentId);
     List<BehaviorRecord> find(Long assignmentId, Long studentId);
     String upload(Long assignmentId, Long studentId, MultipartFile file);
     String upload(Long assignmentId, Long studentId, MultipartFile file);
+
+    BehaviorRecordVO findALL(Long assignmentId, Long studentId);
 }
 }

+ 74 - 2
src/main/java/com/njuzr/eaibackend/service/impl/BehaviorRecordServiceImpl.java

@@ -6,6 +6,7 @@ import com.njuzr.eaibackend.mapper.BehaviorRecordMapper;
 import com.njuzr.eaibackend.po.BehaviorRecord;
 import com.njuzr.eaibackend.po.BehaviorRecord;
 import com.njuzr.eaibackend.service.BehaviorRecordService;
 import com.njuzr.eaibackend.service.BehaviorRecordService;
 import com.njuzr.eaibackend.utils.OssUtil;
 import com.njuzr.eaibackend.utils.OssUtil;
+import com.njuzr.eaibackend.vo.BehaviorRecordVO;
 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.http.HttpStatus;
 import org.springframework.http.HttpStatus;
@@ -15,8 +16,17 @@ import org.springframework.web.multipart.MultipartFile;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.Date;
 import java.util.Date;
 import java.util.List;
 import java.util.List;
+import java.util.stream.Collectors;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.data.domain.Sort;
 import org.springframework.data.domain.Sort;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.aggregation.Aggregation;
+import org.springframework.data.mongodb.core.aggregation.AggregationResults;
+
+import java.util.Map;
+
 /**
 /**
  * @author BaiBai
  * @author BaiBai
  * @date 2025-02-10 17:03
  * @date 2025-02-10 17:03
@@ -27,20 +37,82 @@ public class BehaviorRecordServiceImpl implements BehaviorRecordService {
 
 
     private final BehaviorRecordMapper behaviorRecordMapper;
     private final BehaviorRecordMapper behaviorRecordMapper;
     private final ObjectMapper objectMapper;
     private final ObjectMapper objectMapper;
+    private final MongoTemplate mongoTemplate;
 
 
     @Autowired
     @Autowired
-    public BehaviorRecordServiceImpl(BehaviorRecordMapper behaviorRecordMapper, OssUtil ossUtil) {
+    public BehaviorRecordServiceImpl(BehaviorRecordMapper behaviorRecordMapper, OssUtil ossUtil,
+            MongoTemplate mongoTemplate) {
         this.behaviorRecordMapper = behaviorRecordMapper;
         this.behaviorRecordMapper = behaviorRecordMapper;
         this.objectMapper = new ObjectMapper();
         this.objectMapper = new ObjectMapper();
+        this.mongoTemplate = mongoTemplate;
     }
     }
 
 
     @Override
     @Override
     public List<BehaviorRecord> find(Long assignmentId, Long studentId) {
     public List<BehaviorRecord> find(Long assignmentId, Long studentId) {
         // 创建一个按 created_at 降序排序的 Sort 对象,它不会引起额外的排序开销(因为索引已经支持);显式指定排序确保代码的兼容性
         // 创建一个按 created_at 降序排序的 Sort 对象,它不会引起额外的排序开销(因为索引已经支持);显式指定排序确保代码的兼容性
-        Sort sort = Sort.by(Sort.Direction.DESC, "created_at");
+        Sort sort = Sort.by(Sort.Direction.ASC, "created_at");
         return behaviorRecordMapper.findByAssignmentIdAndStudentId(assignmentId, studentId, sort);
         return behaviorRecordMapper.findByAssignmentIdAndStudentId(assignmentId, studentId, sort);
     }
     }
 
 
+    @Override
+    public BehaviorRecordVO findALL(Long assignmentId, Long studentId) {
+        // 使用MongoDB聚合管道,直接在数据库层面展开events并投影所需字段
+        Aggregation aggregation = Aggregation.newAggregation(
+                // 匹配条件 - 使用MongoDB字段名
+                Aggregation.match(Criteria.where("assignment_id").is(assignmentId)
+                        .and("student_id").is(studentId)),
+                // 按创建时间降序排序 - 使用MongoDB字段名
+                Aggregation.sort(Sort.by(Sort.Direction.ASC, "created_at")),
+                // 展开events数组
+                Aggregation.unwind("events"),
+                // 投影所需字段
+                Aggregation.project()
+                        .and("events.type").as("type")
+                        .and("events.retain").as("retain")
+                        .and("events.insert").as("insert")
+                        .and("events.startStamp").as("startStamp")
+                        .and("events.endStamp").as("endStamp")
+                        .and("events.pauseTime").as("pauseTime")
+                        .and("created_at").as("createdAt"));
+
+        AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, "behaviorRecords", Map.class);
+        List<Map> mappedResults = results.getMappedResults();
+
+        BehaviorRecordVO vo = new BehaviorRecordVO();
+        vo.setStudentId(studentId);
+        vo.setAssignmentId(assignmentId);
+
+        if (!mappedResults.isEmpty()) {
+            // 获取第一条记录的创建时间
+            vo.setCreatedAt((Date) mappedResults.get(0).get("createdAt"));
+
+            // 转换为SimplifiedEvent列表
+            List<BehaviorRecordVO.SimplifiedEvent> events = mappedResults.stream()
+                    .map(this::mapToSimplifiedEvent)
+                    .collect(Collectors.toList());
+
+            vo.setEvents(events);
+        }
+
+        return vo;
+    }
+
+    /**
+     * 将Map转换为SimplifiedEvent
+     */
+    private BehaviorRecordVO.SimplifiedEvent mapToSimplifiedEvent(Map<String, Object> map) {
+        BehaviorRecordVO.SimplifiedEvent simplified = new BehaviorRecordVO.SimplifiedEvent();
+
+        simplified.setType((String) map.get("type"));
+        simplified.setRetain((Integer) map.get("retain"));
+        simplified.setInsert((String) map.get("insert"));
+        simplified.setStartStamp((Long) map.get("startStamp"));
+        simplified.setEndStamp((Long) map.get("endStamp"));
+        simplified.setPauseTime((Long) map.get("pauseTime"));
+
+        return simplified;
+    }
+
 //    public String upload(Long assignmentId, Long studentId, MultipartFile file) {
 //    public String upload(Long assignmentId, Long studentId, MultipartFile file) {
 //        try {
 //        try {
 //            Date now = new Date();
 //            Date now = new Date();

+ 37 - 0
src/main/java/com/njuzr/eaibackend/vo/BehaviorRecordVO.java

@@ -0,0 +1,37 @@
+package com.njuzr.eaibackend.vo;
+
+import lombok.Data;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @Auther: WuZilong
+ * @Date: 2025/7/17 00:15
+ */
+@Data
+public class BehaviorRecordVO {
+    private Long studentId; // 学生ID
+    private Long assignmentId; // 作业ID
+    private Date createdAt; // 所有记录中,首次记录创建的时间
+
+    private List<SimplifiedEvent> events;
+    /**
+     * 嵌套行为事件类
+     */
+    @Data
+    public static class SimplifiedEvent  {
+        // 基础事件信息
+        private String type;           // 事件类型(cursor_move/insert/delete等)
+        private Integer retain;        // 光标位置
+
+        // 文本操作相关
+        private String insert;         // 插入的文本内容
+
+        // 时间戳信息
+        private Long startStamp;     // 开始时间戳
+
+        // 结束时间信息
+        private Long endStamp;       // 结束时间戳
+        private Long pauseTime;      // 停留时间(毫秒)
+    }
+}