package com.njuzr.eaibackend.service.impl; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.njuzr.eaibackend.dto.AssignmentDTO; import com.njuzr.eaibackend.dto.AssignmentQueryDTO; import com.njuzr.eaibackend.dto.AssignmentUpdateDTO; import com.njuzr.eaibackend.dto.CorrectDTO; import com.njuzr.eaibackend.enums.AssignmentCompletionStatus; import com.njuzr.eaibackend.enums.AssignmentStatus; import com.njuzr.eaibackend.enums.Role; import com.njuzr.eaibackend.exception.MyException; import com.njuzr.eaibackend.mapper.*; import com.njuzr.eaibackend.po.*; import com.njuzr.eaibackend.po.Class; import com.njuzr.eaibackend.service.AssignmentService; import com.njuzr.eaibackend.service.ClassService; import com.njuzr.eaibackend.service.TextAnalysisService; import com.njuzr.eaibackend.utils.*; import com.njuzr.eaibackend.vo.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.cache.annotation.CacheEvict; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; /** * @author: Leonezhurui * @Date: 2024/3/15 - 00:40 * @Package: EAI-Backend */ @Slf4j @Service public class AssignmentServiceImpl implements AssignmentService { private final AssignmentMapper assignmentMapper; private final CourseMapper courseMapper; private final StudentAssignmentMapper studentAssignmentMapper; private final UserMapper userMapper; private final ClassService classService; private final OssUtil ossUtil; private final FileUtil fileUtil = new FileUtil(); private final TextAnalysisService textAnalysisService; public AssignmentServiceImpl(AssignmentMapper assignmentMapper, CourseMapper courseMapper, StudentAssignmentMapper studentAssignmentMapper, UserMapper userMapper, OssUtil ossUtil, CourseServiceImpl courseServiceImpl, CourseStudentMapper courseStudentMapper, ClassService classService, TextAnalysisService textAnalysisService) { this.assignmentMapper = assignmentMapper; this.courseMapper = courseMapper; this.studentAssignmentMapper = studentAssignmentMapper; this.userMapper = userMapper; this.ossUtil = ossUtil; this.textAnalysisService = textAnalysisService; this.classService = classService; } @Override public AssignmentVO createAssignment(MyUserDetails user, Long courseId, AssignmentDTO assignmentDTO) { Course course = courseMapper.selectById(courseId); if (course == null) throw MyException.create(HttpStatus.BAD_REQUEST, "课程不存在"); if (user.getRole() != Role.ADMIN) { List teacherIds = Collections.singletonList(Optional.ofNullable(course.getTeacherIds()) .orElse(Collections.emptyList().toString())); String userIdStr = String.valueOf(user.getId()); boolean hasTeacherId = teacherIds.contains(userIdStr); boolean pidMatches = Objects.equals(course.getTeacherPid(), user.getPid()); if (!hasTeacherId && !pidMatches) { throw MyException.create(HttpStatus.BAD_REQUEST, "无权限创建"); } } Assignment target = convertToPO(assignmentDTO); target.setCourseId(courseId); target.setTeacherId(user.getId()); target.setEngageNumber(0); target.setCorrectNumber(0); // TODO:权宜之计 // target.setTeacherId(Long.valueOf(user.getPid())); target.setCreateTime(new Date()); try { int code = assignmentMapper.insert(target); if (code == 0) throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "创建作业失败"); } catch (Exception e) { log.error(e.getMessage()); throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "创建作业失败"); } return convertToVO(target); } @Override // @Cacheable(value = "assignmentInfo", key = "'assignment_' + #assignmentId") public AssignmentVO findAssignmentById(Long assignmentId) { QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("assignment_id", assignmentId); Assignment res = assignmentMapper.selectOne(wrapper); log.info("数据库查询作业:{}", assignmentId); if (res == null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在"); return convertToVO(res); } @Override public IPage findAssignmentPage(Page page, AssignmentQueryDTO assignmentQueryDTO) { log.info("作业查询服务:{}", assignmentQueryDTO); QueryWrapper wrapper = new QueryWrapper<>(); if (assignmentQueryDTO.getAssignmentId() != null) { // assignmentId直接判断是否相等 // TODO 检测assignmentId是否正确 wrapper.eq("assignmentId", assignmentQueryDTO.getAssignmentId()); } if (assignmentQueryDTO.getTeacherId() != null) { // teacherId直接匹配 wrapper.eq("teacher_id", assignmentQueryDTO.getTeacherId()); } if (assignmentQueryDTO.getCourseId() != null) { // teacherId直接匹配 wrapper.eq("course_id", assignmentQueryDTO.getCourseId()); } if (assignmentQueryDTO.getAssignmentName() != null) { // 模糊匹配作业名字 wrapper.like("assignment_name", assignmentQueryDTO.getAssignmentName()); } if(assignmentQueryDTO.getType() != null){ wrapper.eq("type",assignmentQueryDTO.getType()); } if (assignmentQueryDTO.getStatus() != null) { AssignmentStatus status = assignmentQueryDTO.getStatus(); if (status == AssignmentStatus.NOT_STARTED) { wrapper.gt("start_time", LocalDateTime.now()); // 意思是start_time > now() } else if (status == AssignmentStatus.PROCEEDING) { wrapper.le("start_time", LocalDateTime.now()) .ge("end_time", LocalDateTime.now()); } else { wrapper.lt("end_time", LocalDateTime.now()); } } IPage assignments = assignmentMapper.selectPage(page, wrapper); return PageMapperUtil.convert(assignments, this::convertToVO); } @Override @CacheEvict(value = "assignmentInfo", key = "'assignment_' + #assignmentId") public AssignmentVO updateAssignment(MyUserDetails user, Long assignmentId, AssignmentUpdateDTO assignmentUpdateDTO) { Assignment target = assignmentMapper.selectById(assignmentId); if (!Objects.equals(target.getTeacherId(), user.getId()) && !Objects.equals(target.getTeacherPid(), user.getPid())) throw MyException.create(HttpStatus.BAD_REQUEST, "无权限更新"); Assignment ope = convertToPO(assignmentUpdateDTO); ope.setAssignmentId(assignmentId); try { int code = assignmentMapper.updateById(ope); if (code == 0) throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "服务器更新失败"); } catch (Exception e) { log.error(e.getMessage()); throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "服务器更新失败"); } return convertToVO(assignmentMapper.selectById(assignmentId)); } /** * 教师删除作业 */ @Override @Transactional @CacheEvict(value = "assignmentInfo", key = "'assignment_' + #assignmentId") public void deleteById(Long teacherId, Long assignmentId) { Assignment target = assignmentMapper.selectById(assignmentId); if (!Objects.equals(target.getTeacherId(), teacherId)) throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "无权限删除"); try { List engagements = studentAssignmentMapper.findEngagementByAssignmentId(assignmentId); for(Engagement engagement : engagements) { studentAssignmentMapper.delete(engagement.getStudentId(), assignmentId); } int code = assignmentMapper.deleteById(assignmentId); if (code == 0) { throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "删除作业错误"); } } catch (Exception e) { log.error(e.getMessage()); throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "删除作业错误"); } } @Override public void engageAssignment(Long studentId, Long assignmentId) { if(studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId) != null){ return; } // 1创建Engagement实体类,初始化基本信息 Engagement engagement = new Engagement(); engagement.setStudentId(studentId); engagement.setAssignmentId(assignmentId); engagement.setVersion(1); // 设置这是version engagement.setStatus(AssignmentCompletionStatus.NOT_SUBMITTED); // 已经进入到页面编辑,但是还没有提交 Assignment assignment = assignmentMapper.selectById(assignmentId); assignment.setEngageNumber(assignment.getEngageNumber() + 1); // // 2通过FileUtil创建一个空的docx文件 // InputStream inputStream = fileUtil.createEmptyDocx(); // // String filePath = assignmentId + "_" + studentId + ".docx"; // // 3通过OssUtil工具类上传空文件,注意给其命名为`assignmentId/studentId.docx`,并得到文件链接 // String fileName = ossUtil.uploadEnduringFile(inputStream, filePath); // engagement.setFileUrl(fileName); try { int code = studentAssignmentMapper.insert(engagement); int code1 = assignmentMapper.updateById(assignment); if (code == 0 || code1 == 0) throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "参加作业失败"); } catch (Exception e) { log.error(e.getMessage()); // ossUtil.deleteFile(filePath); throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "参加作业失败"); } } /** * 根据两个外键,搜索学生参与作业的情况(需要在mysql中设置索引) * * @param studentId * @param assignmentId * @return */ @Override public EngagementVO getEngagement(Long studentId, Long assignmentId) { Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId); if (engagement == null) throw MyException.create(HttpStatus.BAD_REQUEST, "参与作业情况不存在"); return convertToVO(engagement); } // 返回顺序: 先是未批改的,然后是批改的按照成绩从高到低返回 @Override public IPage findEngagementPage(Page page, Long assignmentId) { IPage engagements = studentAssignmentMapper.findEngagementByAssignmentId(page, assignmentId); return PageMapperUtil.convert(engagements, this::convertToVO); } @Override public EngagementDetailVO getDetailEngagement(Long studentId, Long assignmentId) { // 1 获取用户详细信息 User student = userMapper.selectById(studentId); if (student == null) throw MyException.create(HttpStatus.BAD_REQUEST, "学生不存在"); // 2 获取作业详细信息 Assignment assignment = assignmentMapper.selectById(assignmentId); if (assignment == null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在"); // 3 获取作业参与详细信息 Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId); if (engagement == null) throw MyException.create(HttpStatus.BAD_REQUEST, "参与作业情况不存在"); EngagementDetailVO detailVO = ModelMapperUtil.map(engagement, EngagementDetailVO.class); detailVO.setUserVO(ModelMapperUtil.map(student, UserVO.class)); detailVO.setAssignmentVO(convertToVO(assignment)); // detailVO.setFileKey(assignmentId + "_" + studentId + "_" + engagement.getVersion()); log.info("请求到详细信息:" + detailVO); return detailVO; } /** * 作业提交,更新文件版本和作业状态 * * @param studentId * @param assignmentId */ @Override public void engagementSubmit(Long studentId, Long assignmentId) { Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId); if (engagement == null) throw MyException.create(HttpStatus.BAD_REQUEST, "参与作业情况不存在"); int originalVersion = engagement.getVersion(); engagement.setVersion(originalVersion + 1); engagement.setStatus(AssignmentCompletionStatus.SUBMITTED); try { int code = studentAssignmentMapper.updateById(engagement); if (code == 0) throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败"); } catch (Exception e) { log.error(e.getMessage()); throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败"); } } /** * 作业暂存,更新文件版本,不修改作业状态 * * @param studentId * @param assignmentId */ @Override public void engagementStage(Long studentId, Long assignmentId, JSONObject jsonObject) { Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId); if (engagement == null) throw MyException.create(HttpStatus.BAD_REQUEST, "参与作业情况不存在"); int originalVersion = engagement.getVersion(); String quillContent = jsonObject.getString("quillContent"); String textContent = jsonObject.getString("textContent"); String htmlContent = jsonObject.getString("htmlContent"); engagement.setVersion(originalVersion + 1); engagement.setQuillContent(quillContent); engagement.setTextContent(textContent); engagement.setHtmlContent(htmlContent); try { int code = studentAssignmentMapper.updateById(engagement); if (code == 0) throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败"); // 异步启动文本分析,不影响主流程返回速度 try { textAnalysisService.analyzeAndSaveTextContentAsync(studentId, assignmentId); log.info("已启动异步文本分析 - studentId: {}, assignmentId: {}", studentId, assignmentId); } catch (Exception e) { log.error("启动异步文本分析失败 - studentId: {}, assignmentId: {}, error: {}", studentId, assignmentId, e.getMessage()); // 不抛出异常,避免影响主流程 } } catch (Exception e) { log.error(e.getMessage()); throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败"); } } @Override public void engagementCorrect(Long teacherId, Long studentId, Long assignmentId, CorrectDTO correctDTO) { Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId); if (engagement == null) throw MyException.create(HttpStatus.BAD_REQUEST, "参与作业情况不存在"); Assignment assignment = assignmentMapper.selectById(assignmentId); if (assignment == null) throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在"); if (!Objects.equals(assignment.getTeacherId(), teacherId)) throw MyException.create(HttpStatus.BAD_REQUEST, "无权限批改"); if(engagement.getStatus() != AssignmentCompletionStatus.CORRECTED){ assignment.setCorrectNumber(assignment.getCorrectNumber() + 1); engagement.setStatus(AssignmentCompletionStatus.CORRECTED); } engagement.setScore(correctDTO.getScore()); engagement.setRemark(correctDTO.getRemark()); try { int code = studentAssignmentMapper.updateById(engagement); int code1 = assignmentMapper.updateById(assignment); if (code == 0 || code1 == 0) throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "批改作业失败"); } catch (Exception e) { log.error(e.getMessage()); throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "批改作业失败"); } } @Override public long getNextStudentId(Long assignmentId, Long studentId) { List allRecords = studentAssignmentMapper.selectList( new QueryWrapper() .eq("assignment_id", assignmentId) // 可以在此添加其他查询条件 ); // 对记录进行排序:status为4或6的记录排到最后,其他记录保持原始顺序 List sortedList = allRecords.stream() .sorted(Comparator.comparing( e -> e.getStatus() == AssignmentCompletionStatus.PUBLISHED || e.getStatus() == AssignmentCompletionStatus.CORRECTED )).toList(); int currentIndex = -1; for (int i = 0; i < sortedList.size(); i++) { if (sortedList.get(i).getStudentId().equals(studentId)) { currentIndex = i; break; } } if (currentIndex == allRecords.size() - 1 || currentIndex == -1) { return sortedList.get(0).getStudentId(); } return sortedList.get(currentIndex + 1).getStudentId(); } @Override public EngageNumberVo getEngageNumber(Long assignmentId) { Assignment assignment = assignmentMapper.selectById(assignmentId); if(assignment == null){ throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在"); } if(assignment.getEngageNumber() != null && assignment.getCorrectNumber() != null){ return new EngageNumberVo(assignment.getEngageNumber(), assignment.getCorrectNumber()); } else { throw MyException.create(HttpStatus.BAD_REQUEST, "未建立作业统计"); } } private Assignment convertToPO(AssignmentDTO assignmentDTO) { Assignment assignment = new Assignment(); BeanUtils.copyProperties(assignmentDTO, assignment); if (assignmentDTO.getAttachments() != null) assignment.setAttachments(ConvertUtil.listToString(assignmentDTO.getAttachments())); return assignment; } private Assignment convertToPO(AssignmentUpdateDTO assignmentDTO) { Assignment assignment = new Assignment(); BeanUtils.copyProperties(assignmentDTO, assignment); if (assignmentDTO.getAttachments() != null) assignment.setAttachments(ConvertUtil.listToString(assignmentDTO.getAttachments())); return assignment; } private AssignmentVO convertToVO(Assignment assignment) { AssignmentVO res = new AssignmentVO(); BeanUtils.copyProperties(assignment, res); if (assignment.getAttachments() != null) res.setAttachments(ConvertUtil.stringToList(assignment.getAttachments(), String::valueOf)); res.setStatus(calculateStatus(res.getStartTime(), res.getEndTime())); // 组装老师姓名 res.setTeacherName(userMapper.selectNameById(assignment.getTeacherId())); return res; } private EngagementVO convertToVO(Engagement engagement) { EngagementVO res = ModelMapperUtil.map(engagement, EngagementVO.class); // 组装文件key // res.setFileKey(res.getAssignmentId() + "_" + res.getStudentId() + "_" + res.getVersion()); // 组装学生姓名 String studentName = userMapper.selectNameById(engagement.getStudentId()); res.setStudentName(studentName); return res; } private AssignmentStatus calculateStatus(Date startTime, Date endTime) { Date now = new Date(); if (now.before(startTime)) { return AssignmentStatus.NOT_STARTED; } else if (now.after(endTime)) { return AssignmentStatus.FINISHED; } else { return AssignmentStatus.PROCEEDING; } } @Override public List getStudentsAssignmentStatus(Long classId, Long assignmentId) { // 1. 验证班级和作业存在性 Assignment assignment = assignmentMapper.selectById(assignmentId); if (assignment == null) { throw new RuntimeException("作业不存在"); // 建议替换为项目自定义异常 } // 2. 获取班级所有学生(从class_student表) List classStudents = classService.getStudentsByClassId(classId); if (classStudents.isEmpty()) { return Collections.emptyList(); } // 3. 提取学号列表,查询对应的用户ID(users表) List officialNumbers = classStudents.stream() .map(StudentInfoVO::getOfficialNumber) .collect(Collectors.toList()); List users = userMapper.selectUserIdByOfficialNumbers(officialNumbers); Map officialNumberToUserIdMap = users.stream() .collect(Collectors.toMap( User::getOfficialNumber, User::getId )); // 4. 查询这些学生的作业提交情况(student_assignment表) List studentIds = new ArrayList<>(officialNumberToUserIdMap.values()); List studentAssignments = studentAssignmentMapper.findByAssignmentIdAndStudentIds(assignmentId, studentIds); // 5. 构建学生ID到作业情况的映射 Map studentIdToAssignmentMap = studentAssignments.stream() .collect(Collectors.toMap( StudentAssignment::getStudentId, Function.identity() )); // 6. 组装返回结果 return classStudents.stream() .map(student -> { StudentAssignmentStatusVO vo = new StudentAssignmentStatusVO(); vo.setOfficialNumber(student.getOfficialNumber()); vo.setStuName(student.getStuName()); // 设置学生ID Long studentId = officialNumberToUserIdMap.get(student.getOfficialNumber()); vo.setStudentId(studentId); // 设置状态和成绩 StudentAssignment studentAssignment = studentIdToAssignmentMap.get(studentId); if (studentAssignment == null) { // 未参与作业,按未提交处理 vo.setStatus(AssignmentCompletionStatus.NOT_SUBMITTED); vo.setScore(0); } else { // 转换状态为枚举 vo.setStatus(AssignmentCompletionStatus.fromValue(studentAssignment.getStatus())); // 处理成绩(BigDecimal转Integer,未批改为0) vo.setScore(studentAssignment.getScore() != null ? studentAssignment.getScore().intValue() : 0); } return vo; }) .collect(Collectors.toList()); } }