|
|
@@ -0,0 +1,221 @@
|
|
|
+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.vo.BehaviorOverviewVO;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.Map;
|
|
|
+import org.apache.poi.ss.usermodel.Cell;
|
|
|
+import org.apache.poi.ss.usermodel.Row;
|
|
|
+import org.apache.poi.ss.usermodel.Sheet;
|
|
|
+import org.apache.poi.ss.usermodel.Workbook;
|
|
|
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
+import java.io.IOException;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 将 behaviorOverviews 的数据导出为 Excel
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class ExportBehaviorOverviewService {
|
|
|
+
|
|
|
+ private final BehaviorOverviewService behaviorOverviewService;
|
|
|
+ private final BehaviorOverviewMapper behaviorOverviewMapper;
|
|
|
+ private final UserService userService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ public ExportBehaviorOverviewService(BehaviorOverviewService behaviorOverviewService, BehaviorOverviewMapper behaviorOverviewMapper, UserService userService) {
|
|
|
+ this.behaviorOverviewService = behaviorOverviewService;
|
|
|
+ this.behaviorOverviewMapper = behaviorOverviewMapper;
|
|
|
+ this.userService = userService;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成Excel字节流,包含两个sheet:Summary 与 Events
|
|
|
+ */
|
|
|
+ public byte[] exportBehaviorOverviewExcel(Long assignmentId, Long studentId) {
|
|
|
+ BehaviorOverviewVO overview = behaviorOverviewService.getBehaviorOverview(studentId, assignmentId);
|
|
|
+
|
|
|
+ try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
|
|
+ // Summary sheet
|
|
|
+ Sheet summary = workbook.createSheet("Summary");
|
|
|
+ createSummarySheet(summary, overview);
|
|
|
+
|
|
|
+ // Events sheet(可选:当事件较大时可能会很长)
|
|
|
+ Sheet eventsSheet = workbook.createSheet("Events");
|
|
|
+ createEventsSheet(eventsSheet, studentId, assignmentId);
|
|
|
+
|
|
|
+ workbook.write(baos);
|
|
|
+ return baos.toByteArray();
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "导出Excel失败" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 导出指定 assignmentId 下所有学生的数据到一个 Excel
|
|
|
+ * Sheet1: Summary(每行一个学生概览)
|
|
|
+ * Sheet2..N: 每个学生一个 Events 明细表(按 studentId 命名,避免超长或重复名冲突)
|
|
|
+ */
|
|
|
+ public byte[] exportAssignmentBehaviorOverviewExcel(Long assignmentId) {
|
|
|
+ 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", "assignmentId", "insertCount", "deleteCount", "copyCount", "copyCharacterCount", "longPauseCount"
|
|
|
+ };
|
|
|
+ for (int i = 0; i < headers.length; i++) {
|
|
|
+ header.createCell(i).setCellValue(headers[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询该作业下所有概览
|
|
|
+ java.util.List<BehaviorOverview> overviews = behaviorOverviewMapper.findByAssignmentId(assignmentId);
|
|
|
+ if (overviews != null) {
|
|
|
+ // 先批量拉取 studentId -> name 映射,避免 N 次查询
|
|
|
+ Set<Long> studentIds = overviews.stream()
|
|
|
+ .map(BehaviorOverview::getStudentId)
|
|
|
+ .filter(java.util.Objects::nonNull)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ Map<Long, String> idToName = userService.getNamesByIds(studentIds);
|
|
|
+ for (BehaviorOverview ov : overviews) {
|
|
|
+ BehaviorOverviewVO vo = behaviorOverviewService.getBehaviorOverview(ov.getStudentId(), assignmentId);
|
|
|
+ Row row = summary.createRow(rowIdx++);
|
|
|
+ int col = 0;
|
|
|
+ String name = idToName != null ? idToName.get(vo.getStudentId()) : null;
|
|
|
+ row.createCell(col++).setCellValue(name == null ? "" : name);
|
|
|
+ row.createCell(col++).setCellValue(vo.getStudentId() == null ? 0 : vo.getStudentId());
|
|
|
+ row.createCell(col++).setCellValue(vo.getAssignmentId() == null ? 0 : vo.getAssignmentId());
|
|
|
+ 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
|
|
|
+ String sheetName = safeSheetName("S-" + vo.getStudentId() + "-" + (name != null ? name : "Unknown"));
|
|
|
+ Sheet eventsSheet = workbook.createSheet(sheetName);
|
|
|
+ createEventsSheet(eventsSheet, vo.getStudentId(), assignmentId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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\\[\\]]", "-");
|
|
|
+ if (n.length() > 31) {
|
|
|
+ n = n.substring(0, 31);
|
|
|
+ }
|
|
|
+ if (n.isEmpty()) {
|
|
|
+ return "Sheet";
|
|
|
+ }
|
|
|
+ return n;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void createSummarySheet(Sheet sheet, BehaviorOverviewVO overview) {
|
|
|
+ int rowIdx = 0;
|
|
|
+ Row header = sheet.createRow(rowIdx++);
|
|
|
+ String[] headers = new String[]{
|
|
|
+ "studentId", "assignmentId", "insertCount", "deleteCount", "copyCount", "copyCharacterCount", "longPauseCount"
|
|
|
+ };
|
|
|
+ for (int i = 0; i < headers.length; i++) {
|
|
|
+ Cell cell = header.createCell(i);
|
|
|
+ cell.setCellValue(headers[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ Row row = sheet.createRow(rowIdx);
|
|
|
+ int col = 0;
|
|
|
+ row.createCell(col++).setCellValue(overview.getStudentId() == null ? 0 : overview.getStudentId());
|
|
|
+ row.createCell(col++).setCellValue(overview.getAssignmentId() == null ? 0 : overview.getAssignmentId());
|
|
|
+ row.createCell(col++).setCellValue(overview.getInsertCount() == null ? 0 : overview.getInsertCount());
|
|
|
+ row.createCell(col++).setCellValue(overview.getDeleteCount() == null ? 0 : overview.getDeleteCount());
|
|
|
+ row.createCell(col++).setCellValue(overview.getCopyCount() == null ? 0 : overview.getCopyCount());
|
|
|
+ row.createCell(col++).setCellValue(overview.getCopyCharacterCount() == null ? 0 : overview.getCopyCharacterCount());
|
|
|
+ row.createCell(col).setCellValue(overview.getLongPauseCount() == null ? 0 : overview.getLongPauseCount());
|
|
|
+
|
|
|
+ for (int i = 0; i < headers.length; i++) {
|
|
|
+ sheet.autoSizeColumn(i);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void createEventsSheet(Sheet sheet, Long studentId, Long assignmentId) {
|
|
|
+ int rowIdx = 0;
|
|
|
+ Row header = sheet.createRow(rowIdx++);
|
|
|
+ String[] headers = new String[]{
|
|
|
+ "type", "retain", "length", "time", "insert", "docLength", "letterCount",
|
|
|
+ "key", "timeKey", "startStamp", "startClock", "startTime", "endStamp", "endClock", "endTime",
|
|
|
+ "actionTime", "pauseTime", "delete", "oldContent", "deletedContent"
|
|
|
+ };
|
|
|
+ for (int i = 0; i < headers.length; i++) {
|
|
|
+ header.createCell(i).setCellValue(headers[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ BehaviorOverview overview = behaviorOverviewMapper
|
|
|
+ .findByAssignmentIdAndStudentId(assignmentId, studentId)
|
|
|
+ .orElse(null);
|
|
|
+
|
|
|
+ if (overview != null && overview.getEvents() != null && !overview.getEvents().isEmpty()) {
|
|
|
+ for (BehaviorOverview.BehaviorEvent e : overview.getEvents()) {
|
|
|
+ Row row = sheet.createRow(rowIdx++);
|
|
|
+ int col = 0;
|
|
|
+ row.createCell(col++).setCellValue(nullableString(e.getType()));
|
|
|
+ row.createCell(col++).setCellValue(nullableInteger(e.getRetain()));
|
|
|
+ row.createCell(col++).setCellValue(nullableInteger(e.getLength()));
|
|
|
+ row.createCell(col++).setCellValue(nullableString(e.getTime()));
|
|
|
+ row.createCell(col++).setCellValue(nullableString(e.getInsert()));
|
|
|
+ row.createCell(col++).setCellValue(nullableInteger(e.getDocLength()));
|
|
|
+ row.createCell(col++).setCellValue(nullableInteger(e.getLetterCount()));
|
|
|
+ row.createCell(col++).setCellValue(nullableString(e.getKey()));
|
|
|
+ row.createCell(col++).setCellValue(nullableString(e.getTimeKey()));
|
|
|
+ row.createCell(col++).setCellValue(nullableLong(e.getStartStamp()));
|
|
|
+ row.createCell(col++).setCellValue(nullableString(e.getStartClock()));
|
|
|
+ row.createCell(col++).setCellValue(nullableString(e.getStartTime()));
|
|
|
+ row.createCell(col++).setCellValue(nullableLong(e.getEndStamp()));
|
|
|
+ row.createCell(col++).setCellValue(nullableString(e.getEndClock()));
|
|
|
+ row.createCell(col++).setCellValue(nullableString(e.getEndTime()));
|
|
|
+ row.createCell(col++).setCellValue(nullableLong(e.getActionTime()));
|
|
|
+ row.createCell(col++).setCellValue(nullableLong(e.getPauseTime()));
|
|
|
+ row.createCell(col++).setCellValue(nullableString(e.getDelete()));
|
|
|
+ row.createCell(col++).setCellValue(nullableString(e.getOldContent()));
|
|
|
+ row.createCell(col).setCellValue(nullableString(e.getDeletedContent()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = 0; i < headers.length; i++) {
|
|
|
+ sheet.autoSizeColumn(i);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String nullableString(String v) {
|
|
|
+ return v == null ? "" : v;
|
|
|
+ }
|
|
|
+
|
|
|
+ private double nullableInteger(Integer v) {
|
|
|
+ return v == null ? 0 : v;
|
|
|
+ }
|
|
|
+
|
|
|
+ private double nullableLong(Long v) {
|
|
|
+ return v == null ? 0 : v;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 已改为批量映射方式,不再需要单查姓名方法
|
|
|
+}
|
|
|
+
|
|
|
+
|