Browse Source

修复导出功能:1. 解决Excel文件名空格编码问题 2. 修改文件名格式使用学号 3. 修复工作表名称冲突问题 4. 实现班级行为概览导出功能

wuzilong 5 months ago
parent
commit
ecc48b2b56

+ 0 - 7
pom.xml

@@ -167,13 +167,6 @@
             <version>2.0.47</version>
         </dependency>
 
-        <!-- poi-ooxml依赖 -->
-        <dependency>
-            <groupId>org.apache.poi</groupId>
-            <artifactId>poi-ooxml</artifactId>
-            <version>5.3.0</version>
-        </dependency>
-
         <!-- 豆包大语言模型依赖       -->
         <dependency>
             <groupId>com.volcengine</groupId>

+ 61 - 13
src/main/java/com/njuzr/eaibackend/controller/ExportController.java

@@ -1,5 +1,7 @@
 package com.njuzr.eaibackend.controller;
 
+import com.njuzr.eaibackend.mapper.UserMapper;
+import com.njuzr.eaibackend.po.User;
 import com.njuzr.eaibackend.service.ExportCompositionService;
 import com.njuzr.eaibackend.service.ExportBehaviorOverviewService;
 import com.njuzr.eaibackend.service.UserService;
@@ -32,6 +34,8 @@ public class ExportController {
     @Autowired
     private UserService userService;
     @Autowired
+    private UserMapper userMapper;
+    @Autowired
     private AssignmentService assignmentService;
     @Autowired
     private ClassService classService;
@@ -71,18 +75,35 @@ public class ExportController {
 
         HttpHeaders headers = new HttpHeaders();
         headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
-        // 查询学生姓名
+        // 查询学生信息
         String name = null;
+        String officialNumber = null;
+        try {
+            User user = userMapper.selectById(studentId);
+            if (user != null) {
+                name = user.getName();
+                officialNumber = user.getOfficialNumber();
+            }
+        } catch (Exception ignored) { }
+        // 查询作业名称
+        String assignmentName = null;
         try {
-            name = userService.getNameById(studentId);
+            AssignmentVO vo = assignmentService.findAssignmentById(assignmentId);
+            assignmentName = vo != null ? vo.getAssignmentName() : null;
         } catch (Exception ignored) { }
 
-        String fileName = name == null || name.isEmpty()
-                ? String.format("behavior_overview_%d_%d.xlsx", studentId, assignmentId)
-                : String.format("behavior_overview_%d_%d_%s.xlsx", studentId, assignmentId, name);
+        String fileName;
+        if (name == null || name.isEmpty() || officialNumber == null) {
+            fileName = String.format("行为数据记录_%d_%d.xlsx", studentId, assignmentId);
+        } else if (assignmentName == null || assignmentName.isEmpty()) {
+            fileName = String.format("行为数据记录_%s_%s.xlsx", officialNumber, name);
+        } else {
+            fileName = String.format("行为数据记录_%s_%s_%s.xlsx", officialNumber, name, assignmentName);
+        }
 
-        headers.setContentDispositionFormData("attachment",
-                URLEncoder.encode(fileName, StandardCharsets.UTF_8));
+        headers.setContentDisposition(ContentDisposition.builder("attachment")
+                .filename(fileName, StandardCharsets.UTF_8)
+                .build());
 
         return ResponseEntity.ok()
                 .headers(headers)
@@ -102,11 +123,12 @@ public class ExportController {
         } catch (Exception ignored) { }
 
         String fileName = assignmentName == null || assignmentName.isEmpty()
-                ? String.format("behavior_overview_assignment_%d.xlsx", assignmentId)
-                : String.format("behavior_overview_assignment_%d_%s.xlsx", assignmentId, assignmentName);
+                ? String.format("行为数据记录_%d.xlsx", assignmentId)
+                : String.format("行为数据记录_%d_%s.xlsx", assignmentId, assignmentName);
 
-        headers.setContentDispositionFormData("attachment",
-                URLEncoder.encode(fileName, StandardCharsets.UTF_8));
+        headers.setContentDisposition(ContentDisposition.builder("attachment")
+                .filename(fileName, StandardCharsets.UTF_8)
+                .build());
 
         return ResponseEntity.ok()
                 .headers(headers)
@@ -131,8 +153,34 @@ public class ExportController {
                 ? String.format("pause_statistics_assignment_%d_threshold_%d.xlsx", assignmentId, threshold)
                 : String.format("pause_statistics_assignment_%d_%s_threshold_%d.xlsx", assignmentId, assignmentName, threshold);
 
-        headers.setContentDispositionFormData("attachment",
-                URLEncoder.encode(fileName, StandardCharsets.UTF_8));
+        headers.setContentDisposition(ContentDisposition.builder("attachment")
+                .filename(fileName, StandardCharsets.UTF_8)
+                .build());
+
+        return ResponseEntity.ok()
+                .headers(headers)
+                .body(bytes);
+    }
+
+    @GetMapping("/behaviorOverview/excel/class")
+    public ResponseEntity<byte[]> exportClassBehaviorOverviewExcel(@RequestParam Long classId) {
+        byte[] bytes = exportBehaviorOverviewService.exportClassBehaviorOverviewExcel(classId);
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+        String className = null;
+        try {
+            com.njuzr.eaibackend.po.Class cls = classService.getClassById(classId);
+            className = cls != null ? cls.getClassName() : null;
+        } catch (Exception ignored) { }
+
+        String fileName = className == null || className.isEmpty()
+                ? String.format("行为数据记录_%d.xlsx", classId)
+                : String.format("行为数据记录_%d_%s.xlsx", classId, className);
+
+        headers.setContentDisposition(ContentDisposition.builder("attachment")
+                .filename(fileName, StandardCharsets.UTF_8)
+                .build());
 
         return ResponseEntity.ok()
                 .headers(headers)

+ 3 - 0
src/main/java/com/njuzr/eaibackend/mapper/BehaviorOverviewMapper.java

@@ -15,4 +15,7 @@ public interface BehaviorOverviewMapper extends MongoRepository<BehaviorOverview
 
     // 按作业查询该作业下所有学生的概览
     List<BehaviorOverview> findByAssignmentId(Long assignmentId);
+
+    // 按学生查询该学生的所有概览
+    List<BehaviorOverview> findByStudentId(Long studentId);
 }

+ 86 - 1
src/main/java/com/njuzr/eaibackend/service/ExportBehaviorOverviewService.java

@@ -3,7 +3,9 @@ package com.njuzr.eaibackend.service;
 import com.njuzr.eaibackend.exception.MyException;
 import com.njuzr.eaibackend.mapper.BehaviorOverviewMapper;
 import com.njuzr.eaibackend.po.BehaviorOverview;
+import com.njuzr.eaibackend.service.ClassService;
 import com.njuzr.eaibackend.vo.BehaviorOverviewVO;
+import com.njuzr.eaibackend.vo.StudentInfoVO;
 import java.util.stream.Collectors;
 import java.util.Set;
 import java.util.Map;
@@ -18,6 +20,7 @@ import org.springframework.stereotype.Service;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.util.List;
 import com.njuzr.eaibackend.vo.PauseStatisticsVO;
 
 /**
@@ -29,12 +32,14 @@ public class ExportBehaviorOverviewService {
     private final BehaviorOverviewService behaviorOverviewService;
     private final BehaviorOverviewMapper behaviorOverviewMapper;
     private final UserService userService;
+    private final ClassService classService;
 
     @Autowired
-    public ExportBehaviorOverviewService(BehaviorOverviewService behaviorOverviewService, BehaviorOverviewMapper behaviorOverviewMapper, UserService userService) {
+    public ExportBehaviorOverviewService(BehaviorOverviewService behaviorOverviewService, BehaviorOverviewMapper behaviorOverviewMapper, UserService userService, ClassService classService) {
         this.behaviorOverviewService = behaviorOverviewService;
         this.behaviorOverviewMapper = behaviorOverviewMapper;
         this.userService = userService;
+        this.classService = classService;
     }
 
     /**
@@ -179,6 +184,86 @@ public class ExportBehaviorOverviewService {
         }
     }
 
+    /**
+     * 导出指定 classId 下所有学生的行为概览数据为 Excel
+     * Sheet1: Summary(每行一个学生概览)
+     * Sheet2..N: 每个学生一个 Events 明细表(按 studentId 命名,避免超长或重复名冲突)
+     */
+    public byte[] exportClassBehaviorOverviewExcel(Long classId) {
+        try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            // Summary 汇总
+            Sheet summary = workbook.createSheet("Summary");
+            int rowIdx = 0;
+            Row header = summary.createRow(rowIdx++);
+            String[] headers = new String[]{
+                    "name", "studentId", "insertCount", "deleteCount", "copyCount", "copyCharacterCount", "longPauseCount"
+            };
+            for (int i = 0; i < headers.length; i++) {
+                header.createCell(i).setCellValue(headers[i]);
+            }
+
+            // 获取班级下的所有学生
+            List<StudentInfoVO> students = classService.getStudentsByClassId(classId);
+            if (students != null && !students.isEmpty()) {
+                // 先批量拉取 studentId -> name 映射,避免 N 次查询
+                Set<Long> studentIds = students.stream()
+                        .map(StudentInfoVO::getStudentId)
+                        .filter(java.util.Objects::nonNull)
+                        .collect(Collectors.toSet());
+                Map<Long, String> idToName = userService.getNamesByIds(studentIds);
+                
+                for (StudentInfoVO student : students) {
+                    Long studentId = student.getStudentId();
+                    if (studentId == null) continue;
+                    
+                    // 查询该学生的所有行为概览
+                    java.util.List<BehaviorOverview> overviews = behaviorOverviewMapper.findByStudentId(studentId);
+                    if (overviews != null && !overviews.isEmpty()) {
+                        for (BehaviorOverview ov : overviews) {
+                            BehaviorOverviewVO vo = behaviorOverviewService.getBehaviorOverview(studentId, ov.getAssignmentId());
+                            Row row = summary.createRow(rowIdx++);
+                            int col = 0;
+                            String name = idToName != null ? idToName.get(studentId) : student.getStuName();
+                            row.createCell(col++).setCellValue(name == null ? "" : name);
+                            row.createCell(col++).setCellValue(vo.getStudentId() == null ? 0 : vo.getStudentId());
+                            row.createCell(col++).setCellValue(vo.getInsertCount() == null ? 0 : vo.getInsertCount());
+                            row.createCell(col++).setCellValue(vo.getDeleteCount() == null ? 0 : vo.getDeleteCount());
+                            row.createCell(col++).setCellValue(vo.getCopyCount() == null ? 0 : vo.getCopyCount());
+                            row.createCell(col++).setCellValue(vo.getCopyCharacterCount() == null ? 0 : vo.getCopyCharacterCount());
+                            row.createCell(col).setCellValue(vo.getLongPauseCount() == null ? 0 : vo.getLongPauseCount());
+
+                            // 为每个学生的每个作业创建一个事件明细sheet,添加作业ID确保名称唯一
+                            String sheetName = safeSheetName("S-" + vo.getStudentId() + "-" + (name != null ? name : "Unknown") + "-A" + vo.getAssignmentId());
+                            Sheet eventsSheet = workbook.createSheet(sheetName);
+                            createEventsSheet(eventsSheet, vo.getStudentId(), vo.getAssignmentId());
+                        }
+                    } else {
+                        // 没有行为概览数据的学生
+                        Row row = summary.createRow(rowIdx++);
+                        int col = 0;
+                        String name = idToName != null ? idToName.get(studentId) : student.getStuName();
+                        row.createCell(col++).setCellValue(name == null ? "" : name);
+                        row.createCell(col++).setCellValue(studentId);
+                        row.createCell(col++).setCellValue(0);
+                        row.createCell(col++).setCellValue(0);
+                        row.createCell(col++).setCellValue(0);
+                        row.createCell(col++).setCellValue(0);
+                        row.createCell(col).setCellValue(0);
+                    }
+                }
+            }
+
+            for (int i = 0; i < headers.length; i++) {
+                summary.autoSizeColumn(i);
+            }
+
+            workbook.write(baos);
+            return baos.toByteArray();
+        } catch (IOException e) {
+            throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "导出班级行为概览Excel失败" + e.getMessage());
+        }
+    }
+
     private String safeSheetName(String name) {
         // Excel sheet 名称长度限制31,且不允许某些字符
         String n = name.replaceAll("[\\\\/:*?\\n\\r\\[\\]]", "-");