AssignmentServiceImpl.java 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. package com.njuzr.eaibackend.service.impl;
  2. import com.alibaba.fastjson2.JSONObject;
  3. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  4. import com.baomidou.mybatisplus.core.metadata.IPage;
  5. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  6. import com.njuzr.eaibackend.dto.AssignmentDTO;
  7. import com.njuzr.eaibackend.dto.AssignmentQueryDTO;
  8. import com.njuzr.eaibackend.dto.AssignmentUpdateDTO;
  9. import com.njuzr.eaibackend.dto.CorrectDTO;
  10. import com.njuzr.eaibackend.enums.AssignmentCompletionStatus;
  11. import com.njuzr.eaibackend.enums.AssignmentStatus;
  12. import com.njuzr.eaibackend.enums.Role;
  13. import com.njuzr.eaibackend.exception.MyException;
  14. import com.njuzr.eaibackend.mapper.*;
  15. import com.njuzr.eaibackend.po.*;
  16. import com.njuzr.eaibackend.po.Class;
  17. import com.njuzr.eaibackend.service.AssignmentService;
  18. import com.njuzr.eaibackend.service.ClassService;
  19. import com.njuzr.eaibackend.service.ExamTimerService;
  20. import com.njuzr.eaibackend.service.TextAnalysisService;
  21. import com.njuzr.eaibackend.utils.*;
  22. import com.njuzr.eaibackend.vo.*;
  23. import lombok.extern.slf4j.Slf4j;
  24. import org.springframework.beans.BeanUtils;
  25. import org.springframework.cache.annotation.CacheEvict;
  26. import org.springframework.http.HttpStatus;
  27. import org.springframework.stereotype.Service;
  28. import org.springframework.transaction.annotation.Transactional;
  29. import java.time.LocalDateTime;
  30. import java.util.*;
  31. import java.util.function.Function;
  32. import java.util.stream.Collectors;
  33. /**
  34. * @author: Leonezhurui
  35. * @Date: 2024/3/15 - 00:40
  36. * @Package: EAI-Backend
  37. */
  38. @Slf4j
  39. @Service
  40. public class AssignmentServiceImpl implements AssignmentService {
  41. private final AssignmentMapper assignmentMapper;
  42. private final CourseMapper courseMapper;
  43. private final StudentAssignmentMapper studentAssignmentMapper;
  44. private final UserMapper userMapper;
  45. private final ClassService classService;
  46. private final OssUtil ossUtil;
  47. private final ExamTimerService examTimerService;
  48. private final FileUtil fileUtil = new FileUtil();
  49. private final TextAnalysisService textAnalysisService;
  50. public AssignmentServiceImpl(AssignmentMapper assignmentMapper,
  51. CourseMapper courseMapper,
  52. StudentAssignmentMapper studentAssignmentMapper,
  53. UserMapper userMapper,
  54. OssUtil ossUtil,
  55. CourseServiceImpl courseServiceImpl,
  56. CourseStudentMapper courseStudentMapper,
  57. ClassService classService,
  58. TextAnalysisService textAnalysisService,
  59. ExamTimerService examTimerService) {
  60. this.assignmentMapper = assignmentMapper;
  61. this.courseMapper = courseMapper;
  62. this.studentAssignmentMapper = studentAssignmentMapper;
  63. this.userMapper = userMapper;
  64. this.ossUtil = ossUtil;
  65. this.textAnalysisService = textAnalysisService;
  66. this.classService = classService;
  67. this.examTimerService = examTimerService;
  68. }
  69. @Override
  70. public AssignmentVO createAssignment(MyUserDetails user, Long courseId, AssignmentDTO assignmentDTO) {
  71. Course course = courseMapper.selectById(courseId);
  72. if (course == null)
  73. throw MyException.create(HttpStatus.BAD_REQUEST, "课程不存在");
  74. if (user.getRole() != Role.ADMIN) {
  75. List<String> teacherIds = Collections.singletonList(Optional.ofNullable(course.getTeacherIds())
  76. .orElse(Collections.emptyList().toString()));
  77. String userIdStr = String.valueOf(user.getId());
  78. boolean hasTeacherId = teacherIds.contains(userIdStr);
  79. boolean pidMatches = Objects.equals(course.getTeacherPid(), user.getPid());
  80. if (!hasTeacherId && !pidMatches) {
  81. throw MyException.create(HttpStatus.BAD_REQUEST, "无权限创建");
  82. }
  83. }
  84. Assignment target = convertToPO(assignmentDTO);
  85. target.setCourseId(courseId);
  86. target.setTeacherId(user.getId());
  87. target.setEngageNumber(0);
  88. target.setCorrectNumber(0);
  89. // TODO:权宜之计
  90. // target.setTeacherId(Long.valueOf(user.getPid()));
  91. target.setCreateTime(new Date());
  92. try {
  93. int code = assignmentMapper.insert(target);
  94. if (code == 0)
  95. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "创建作业失败");
  96. } catch (Exception e) {
  97. log.error(e.getMessage());
  98. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "创建作业失败");
  99. }
  100. return convertToVO(target);
  101. }
  102. @Override
  103. // @Cacheable(value = "assignmentInfo", key = "'assignment_' + #assignmentId")
  104. public AssignmentVO findAssignmentById(Long assignmentId) {
  105. QueryWrapper<Assignment> wrapper = new QueryWrapper<>();
  106. wrapper.eq("assignment_id", assignmentId);
  107. Assignment res = assignmentMapper.selectOne(wrapper);
  108. log.info("数据库查询作业:{}", assignmentId);
  109. if (res == null)
  110. throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在");
  111. return convertToVO(res);
  112. }
  113. @Override
  114. public IPage<AssignmentVO> findAssignmentPage(Page<Assignment> page, AssignmentQueryDTO assignmentQueryDTO) {
  115. log.info("作业查询服务:{}", assignmentQueryDTO);
  116. QueryWrapper<Assignment> wrapper = new QueryWrapper<>();
  117. if (assignmentQueryDTO.getAssignmentId() != null) { // assignmentId直接判断是否相等
  118. // TODO 检测assignmentId是否正确
  119. wrapper.eq("assignmentId", assignmentQueryDTO.getAssignmentId());
  120. }
  121. if (assignmentQueryDTO.getTeacherId() != null) { // teacherId直接匹配
  122. wrapper.eq("teacher_id", assignmentQueryDTO.getTeacherId());
  123. }
  124. if (assignmentQueryDTO.getCourseId() != null) { // teacherId直接匹配
  125. wrapper.eq("course_id", assignmentQueryDTO.getCourseId());
  126. }
  127. if (assignmentQueryDTO.getAssignmentName() != null) { // 模糊匹配作业名字
  128. wrapper.like("assignment_name", assignmentQueryDTO.getAssignmentName());
  129. }
  130. if(assignmentQueryDTO.getType() != null){
  131. wrapper.eq("type",assignmentQueryDTO.getType());
  132. }
  133. if (assignmentQueryDTO.getStatus() != null) {
  134. AssignmentStatus status = assignmentQueryDTO.getStatus();
  135. if (status == AssignmentStatus.NOT_STARTED) {
  136. wrapper.gt("start_time", LocalDateTime.now()); // 意思是start_time > now()
  137. } else if (status == AssignmentStatus.PROCEEDING) {
  138. wrapper.le("start_time", LocalDateTime.now())
  139. .ge("end_time", LocalDateTime.now());
  140. } else {
  141. wrapper.lt("end_time", LocalDateTime.now());
  142. }
  143. }
  144. IPage<Assignment> assignments = assignmentMapper.selectPage(page, wrapper);
  145. return PageMapperUtil.convert(assignments, this::convertToVO);
  146. }
  147. @Override
  148. @CacheEvict(value = "assignmentInfo", key = "'assignment_' + #assignmentId")
  149. public AssignmentVO updateAssignment(MyUserDetails user, Long assignmentId, AssignmentUpdateDTO assignmentUpdateDTO) {
  150. Assignment target = assignmentMapper.selectById(assignmentId);
  151. if (!Objects.equals(target.getTeacherId(), user.getId()) && !Objects.equals(target.getTeacherPid(), user.getPid()))
  152. throw MyException.create(HttpStatus.BAD_REQUEST, "无权限更新");
  153. Assignment ope = convertToPO(assignmentUpdateDTO);
  154. ope.setAssignmentId(assignmentId);
  155. try {
  156. int code = assignmentMapper.updateById(ope);
  157. if (code == 0)
  158. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "服务器更新失败");
  159. } catch (Exception e) {
  160. log.error(e.getMessage());
  161. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "服务器更新失败");
  162. }
  163. return convertToVO(assignmentMapper.selectById(assignmentId));
  164. }
  165. /**
  166. * 教师删除作业
  167. */
  168. @Override
  169. @Transactional
  170. @CacheEvict(value = "assignmentInfo", key = "'assignment_' + #assignmentId")
  171. public void deleteById(Long teacherId, Long assignmentId) {
  172. Assignment target = assignmentMapper.selectById(assignmentId);
  173. if (!Objects.equals(target.getTeacherId(), teacherId))
  174. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "无权限删除");
  175. try {
  176. List<Engagement> engagements = studentAssignmentMapper.findEngagementByAssignmentId(assignmentId);
  177. for(Engagement engagement : engagements) {
  178. studentAssignmentMapper.delete(engagement.getStudentId(), assignmentId);
  179. }
  180. int code = assignmentMapper.deleteById(assignmentId);
  181. if (code == 0) {
  182. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "删除作业错误");
  183. }
  184. } catch (Exception e) {
  185. log.error(e.getMessage());
  186. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "删除作业错误");
  187. }
  188. }
  189. @Override
  190. public void engageAssignment(Long studentId, Long assignmentId) {
  191. if(studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId) != null){
  192. return;
  193. }
  194. // 1创建Engagement实体类,初始化基本信息
  195. Engagement engagement = new Engagement();
  196. engagement.setStudentId(studentId);
  197. engagement.setAssignmentId(assignmentId);
  198. engagement.setVersion(0); // 初始版本从0开始,首次提交后变为1
  199. engagement.setStatus(AssignmentCompletionStatus.NOT_SUBMITTED); // 已经进入到页面编辑,但是还没有提交
  200. Assignment assignment = assignmentMapper.selectById(assignmentId);
  201. assignment.setEngageNumber(assignment.getEngageNumber() + 1);
  202. if (Boolean.TRUE.equals(assignment.getExamMode()) && assignment.getExamDuration() != null) {
  203. examTimerService.startExamTimer(studentId, assignmentId, assignment.getExamDuration());
  204. }
  205. // // 2通过FileUtil创建一个空的docx文件
  206. // InputStream inputStream = fileUtil.createEmptyDocx();
  207. //
  208. // String filePath = assignmentId + "_" + studentId + ".docx";
  209. // // 3通过OssUtil工具类上传空文件,注意给其命名为`assignmentId/studentId.docx`,并得到文件链接
  210. // String fileName = ossUtil.uploadEnduringFile(inputStream, filePath);
  211. // engagement.setFileUrl(fileName);
  212. try {
  213. int code = studentAssignmentMapper.insert(engagement);
  214. int code1 = assignmentMapper.updateById(assignment);
  215. if (code == 0 || code1 == 0)
  216. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "参加作业失败");
  217. } catch (Exception e) {
  218. log.error(e.getMessage());
  219. // ossUtil.deleteFile(filePath);
  220. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "参加作业失败");
  221. }
  222. }
  223. /**
  224. * 根据两个外键,搜索学生参与作业的情况(需要在mysql中设置索引)
  225. *
  226. * @param studentId
  227. * @param assignmentId
  228. * @return
  229. */
  230. @Override
  231. public EngagementVO getEngagement(Long studentId, Long assignmentId) {
  232. Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
  233. if (engagement == null)
  234. throw MyException.create(HttpStatus.BAD_REQUEST, "参与作业情况不存在");
  235. return convertToVO(engagement);
  236. }
  237. // 返回顺序: 先是未批改的,然后是批改的按照成绩从高到低返回
  238. @Override
  239. public IPage<EngagementVO> findEngagementPage(Page<Engagement> page, Long assignmentId) {
  240. IPage<Engagement> engagements = studentAssignmentMapper.findEngagementByAssignmentId(page, assignmentId);
  241. return PageMapperUtil.convert(engagements, this::convertToVO);
  242. }
  243. @Override
  244. public EngagementDetailVO getDetailEngagement(Long studentId, Long assignmentId) {
  245. // 1 获取用户详细信息
  246. User student = userMapper.selectById(studentId);
  247. if (student == null)
  248. throw MyException.create(HttpStatus.BAD_REQUEST, "学生不存在");
  249. // 2 获取作业详细信息
  250. Assignment assignment = assignmentMapper.selectById(assignmentId);
  251. if (assignment == null)
  252. throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在");
  253. // 3 获取作业参与详细信息
  254. Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
  255. if (engagement == null)
  256. throw MyException.create(HttpStatus.BAD_REQUEST, "参与作业情况不存在");
  257. EngagementDetailVO detailVO = ModelMapperUtil.map(engagement, EngagementDetailVO.class);
  258. detailVO.setUserVO(ModelMapperUtil.map(student, UserVO.class));
  259. detailVO.setAssignmentVO(convertToVO(assignment));
  260. // detailVO.setFileKey(assignmentId + "_" + studentId + "_" + engagement.getVersion());
  261. log.info("请求到详细信息:" + detailVO);
  262. return detailVO;
  263. }
  264. @Override
  265. public List<EngagementHistoryVO> getEngagementHistory(Long studentId, Long assignmentId) {
  266. Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
  267. if (engagement == null) {
  268. throw MyException.create(HttpStatus.BAD_REQUEST, "参与作业情况不存在");
  269. }
  270. List<StudentAssignmentHistory> historyRecords =
  271. studentAssignmentMapper.findHistoryByStudentIdAndAssignmentId(studentId, assignmentId);
  272. return historyRecords.stream()
  273. .map(item -> {
  274. EngagementHistoryVO vo = new EngagementHistoryVO();
  275. vo.setVersion(item.getVersion());
  276. vo.setQuillContent(item.getQuillContent());
  277. vo.setTextContent(item.getTextContent());
  278. vo.setHtmlContent(item.getHtmlContent());
  279. vo.setSubmitTime(item.getSubmitTime());
  280. if (item.getStatus() != null) {
  281. vo.setStatus(new AssignmentCompletionStatusVO(item.getStatus().getCode(), item.getStatus().getStatus()));
  282. }
  283. return vo;
  284. })
  285. .toList();
  286. }
  287. @Override
  288. public List<EngagementHistoryVO> getEngagementHistoryAllowEmpty(Long studentId, Long assignmentId) {
  289. Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
  290. if (engagement == null) {
  291. return Collections.emptyList();
  292. }
  293. List<StudentAssignmentHistory> historyRecords =
  294. studentAssignmentMapper.findHistoryByStudentIdAndAssignmentId(studentId, assignmentId);
  295. return historyRecords.stream()
  296. .map(item -> {
  297. EngagementHistoryVO vo = new EngagementHistoryVO();
  298. vo.setVersion(item.getVersion());
  299. vo.setQuillContent(item.getQuillContent());
  300. vo.setTextContent(item.getTextContent());
  301. vo.setHtmlContent(item.getHtmlContent());
  302. vo.setSubmitTime(item.getSubmitTime());
  303. if (item.getStatus() != null) {
  304. vo.setStatus(new AssignmentCompletionStatusVO(item.getStatus().getCode(), item.getStatus().getStatus()));
  305. }
  306. return vo;
  307. })
  308. .toList();
  309. }
  310. /**
  311. * 作业提交,更新文件版本和作业状态
  312. *
  313. * @param studentId
  314. * @param assignmentId
  315. */
  316. @Override
  317. @Transactional
  318. public void engagementSubmit(Long studentId, Long assignmentId) {
  319. log.info("studentId: {}, assignmentId: {}", studentId, assignmentId);
  320. Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
  321. if (engagement == null) {
  322. log.error("参与作业情况不存在 - studentId: {}, assignmentId: {}", studentId, assignmentId);
  323. throw MyException.create(HttpStatus.BAD_REQUEST, "参与作业情况不存在");
  324. }
  325. int originalVersion = engagement.getVersion();
  326. engagement.setVersion(originalVersion + 1);
  327. engagement.setStatus(AssignmentCompletionStatus.SUBMITTED);
  328. Assignment assignment = assignmentMapper.selectById(assignmentId);
  329. if (Boolean.TRUE.equals(assignment.getExamMode())) {
  330. examTimerService.submitExam(studentId, assignmentId);
  331. engagement.setSubmitted(true);
  332. }
  333. try {
  334. int code = studentAssignmentMapper.updateById(engagement);
  335. if (code == 0) {
  336. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");
  337. }
  338. // 基于主表当前行落一条快照(幂等),避免漏写/并发导致 history 缺版本
  339. studentAssignmentMapper.insertHistorySnapshot(assignmentId, studentId);
  340. } catch (Exception e) {
  341. log.error("更新异常: {}", e.getMessage(), e);
  342. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");
  343. }
  344. }
  345. /**
  346. * 补交作业,更新文件版本和作业状态为已补交
  347. *
  348. * @param studentId
  349. * @param assignmentId
  350. */
  351. @Override
  352. @Transactional
  353. public void engagementLateSubmit(Long studentId, Long assignmentId) {
  354. Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
  355. if (engagement == null)
  356. throw MyException.create(HttpStatus.BAD_REQUEST, "参与作业情况不存在");
  357. int originalVersion = engagement.getVersion();
  358. engagement.setVersion(originalVersion + 1);
  359. engagement.setStatus(AssignmentCompletionStatus.LATE_SUBMITTED);
  360. Assignment assignment = assignmentMapper.selectById(assignmentId);
  361. if (Boolean.TRUE.equals(assignment.getExamMode())) {
  362. examTimerService.submitExam(studentId, assignmentId);
  363. engagement.setSubmitted(true);
  364. }
  365. try {
  366. int code = studentAssignmentMapper.updateById(engagement);
  367. if (code == 0)
  368. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");
  369. studentAssignmentMapper.insertHistorySnapshot(assignmentId, studentId);
  370. } catch (Exception e) {
  371. log.error(e.getMessage());
  372. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");
  373. }
  374. }
  375. /**
  376. * 作业暂存,更新文件版本,不修改作业状态
  377. *
  378. * @param studentId
  379. * @param assignmentId
  380. */
  381. @Override
  382. public void engagementStage(Long studentId, Long assignmentId, JSONObject jsonObject) {
  383. Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
  384. if (engagement == null)
  385. throw MyException.create(HttpStatus.BAD_REQUEST, "参与作业情况不存在");
  386. log.info("[ENGAGE][STAGE] start assignmentId={}, studentId={}, version(before)={}, status={}",
  387. assignmentId, studentId, engagement.getVersion(), engagement.getStatus());
  388. String quillContent = jsonObject.getString("quillContent");
  389. String textContent = jsonObject.getString("textContent");
  390. String htmlContent = jsonObject.getString("htmlContent");
  391. engagement.setQuillContent(quillContent);
  392. engagement.setTextContent(textContent);
  393. engagement.setHtmlContent(htmlContent);
  394. try {
  395. int code = studentAssignmentMapper.updateById(engagement);
  396. if (code == 0)
  397. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");
  398. log.info("[ENGAGE][STAGE] success assignmentId={}, studentId={}, version(after)={}, textLen={}",
  399. assignmentId, studentId, engagement.getVersion(),
  400. textContent == null ? -1 : textContent.length());
  401. // 异步启动文本分析,不影响主流程返回速度
  402. try {
  403. textAnalysisService.analyzeAndSaveTextContentAsync(studentId, assignmentId);
  404. log.info("已启动异步文本分析 - studentId: {}, assignmentId: {}", studentId, assignmentId);
  405. } catch (Exception e) {
  406. log.error("启动异步文本分析失败 - studentId: {}, assignmentId: {}, error: {}",
  407. studentId, assignmentId, e.getMessage());
  408. // 不抛出异常,避免影响主流程
  409. }
  410. } catch (Exception e) {
  411. log.error(e.getMessage());
  412. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");
  413. }
  414. }
  415. @Override
  416. public void engagementCorrect(Long teacherId, Long studentId, Long assignmentId, CorrectDTO correctDTO) {
  417. Engagement engagement = studentAssignmentMapper.findEngagementByStudentIdAndAssignmentId(studentId, assignmentId);
  418. if (engagement == null)
  419. throw MyException.create(HttpStatus.BAD_REQUEST, "参与作业情况不存在");
  420. Assignment assignment = assignmentMapper.selectById(assignmentId);
  421. if (assignment == null)
  422. throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在");
  423. if (!Objects.equals(assignment.getTeacherId(), teacherId))
  424. throw MyException.create(HttpStatus.BAD_REQUEST, "无权限批改");
  425. if(engagement.getStatus() != AssignmentCompletionStatus.CORRECTED && engagement.getStatus() != AssignmentCompletionStatus.LATE_SUBMITTED_CORRECTED){
  426. assignment.setCorrectNumber(assignment.getCorrectNumber() + 1);
  427. if(engagement.getStatus() == AssignmentCompletionStatus.SUBMITTED){
  428. engagement.setStatus(AssignmentCompletionStatus.CORRECTED);
  429. } else if(engagement.getStatus() == AssignmentCompletionStatus.LATE_SUBMITTED){
  430. engagement.setStatus(AssignmentCompletionStatus.LATE_SUBMITTED_CORRECTED);
  431. }
  432. }
  433. engagement.setScore(correctDTO.getScore());
  434. engagement.setRemark(correctDTO.getRemark());
  435. try {
  436. int code = studentAssignmentMapper.updateById(engagement);
  437. int code1 = assignmentMapper.updateById(assignment);
  438. if (code == 0 || code1 == 0)
  439. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "批改作业失败");
  440. } catch (Exception e) {
  441. log.error(e.getMessage());
  442. throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "批改作业失败");
  443. }
  444. }
  445. @Override
  446. public long getNextStudentId(Long assignmentId, Long studentId) {
  447. List<Engagement> allRecords = studentAssignmentMapper.selectList(
  448. new QueryWrapper<Engagement>()
  449. .eq("assignment_id", assignmentId)
  450. .exists("""
  451. SELECT 1
  452. FROM assignment a
  453. JOIN course_student cs ON cs.course_id = a.course_id
  454. WHERE a.assignment_id = student_assignment.assignment_id
  455. AND cs.student_id = student_assignment.student_id
  456. """)
  457. );
  458. if (allRecords.isEmpty()) {
  459. throw MyException.create(HttpStatus.BAD_REQUEST, "当前作业没有可批改的在课学生");
  460. }
  461. // 对记录进行排序:status为4或6的记录排到最后,其他记录保持原始顺序
  462. List<Engagement> sortedList = allRecords.stream()
  463. .sorted(Comparator.comparing(
  464. e -> e.getStatus() == AssignmentCompletionStatus.PUBLISHED ||
  465. e.getStatus() == AssignmentCompletionStatus.CORRECTED
  466. )).toList();
  467. int currentIndex = -1;
  468. for (int i = 0; i < sortedList.size(); i++) {
  469. if (sortedList.get(i).getStudentId().equals(studentId)) {
  470. currentIndex = i;
  471. break;
  472. }
  473. }
  474. if (currentIndex == sortedList.size() - 1 || currentIndex == -1) {
  475. return sortedList.get(0).getStudentId();
  476. }
  477. return sortedList.get(currentIndex + 1).getStudentId();
  478. }
  479. @Override
  480. public EngageNumberVo getEngageNumber(Long assignmentId) {
  481. Assignment assignment = assignmentMapper.selectById(assignmentId);
  482. if(assignment == null){
  483. throw MyException.create(HttpStatus.BAD_REQUEST, "作业不存在");
  484. }
  485. return studentAssignmentMapper.countEngageNumberByCurrentClassStudents(assignmentId);
  486. }
  487. private Assignment convertToPO(AssignmentDTO assignmentDTO) {
  488. Assignment assignment = new Assignment();
  489. BeanUtils.copyProperties(assignmentDTO, assignment);
  490. if (assignmentDTO.getAttachments() != null)
  491. assignment.setAttachments(ConvertUtil.listToString(assignmentDTO.getAttachments()));
  492. return assignment;
  493. }
  494. private Assignment convertToPO(AssignmentUpdateDTO assignmentDTO) {
  495. Assignment assignment = new Assignment();
  496. BeanUtils.copyProperties(assignmentDTO, assignment);
  497. if (assignmentDTO.getAttachments() != null)
  498. assignment.setAttachments(ConvertUtil.listToString(assignmentDTO.getAttachments()));
  499. return assignment;
  500. }
  501. private AssignmentVO convertToVO(Assignment assignment) {
  502. AssignmentVO res = new AssignmentVO();
  503. BeanUtils.copyProperties(assignment, res);
  504. if (assignment.getAttachments() != null)
  505. res.setAttachments(ConvertUtil.stringToList(assignment.getAttachments(), String::valueOf));
  506. res.setStatus(calculateStatus(res.getStartTime(), res.getEndTime()));
  507. // 组装老师姓名
  508. res.setTeacherName(userMapper.selectNameById(assignment.getTeacherId()));
  509. return res;
  510. }
  511. private EngagementVO convertToVO(Engagement engagement) {
  512. EngagementVO res = ModelMapperUtil.map(engagement, EngagementVO.class);
  513. // 组装文件key
  514. // res.setFileKey(res.getAssignmentId() + "_" + res.getStudentId() + "_" + res.getVersion());
  515. // 组装学生姓名
  516. String studentName = userMapper.selectNameById(engagement.getStudentId());
  517. res.setStudentName(studentName);
  518. return res;
  519. }
  520. private void recordHistorySnapshot(Engagement engagement) {
  521. // 检查是否已存在相同版本的历史记录
  522. StudentAssignmentHistory existingHistory = studentAssignmentMapper.findHistoryByAssignmentIdAndStudentIdAndVersion(
  523. engagement.getAssignmentId(), engagement.getStudentId(), engagement.getVersion());
  524. if (existingHistory != null) {
  525. log.info("历史记录已存在,跳过插入 - assignmentId: {}, studentId: {}, version: {}",
  526. engagement.getAssignmentId(), engagement.getStudentId(), engagement.getVersion());
  527. return;
  528. }
  529. StudentAssignmentHistory history = new StudentAssignmentHistory();
  530. history.setAssignmentId(engagement.getAssignmentId());
  531. history.setStudentId(engagement.getStudentId());
  532. history.setVersion(engagement.getVersion());
  533. history.setStatus(engagement.getStatus());
  534. history.setQuillContent(engagement.getQuillContent());
  535. history.setTextContent(engagement.getTextContent());
  536. history.setHtmlContent(engagement.getHtmlContent());
  537. history.setSubmitTime(new Date());
  538. studentAssignmentMapper.insertHistory(history);
  539. log.info("历史记录插入成功 - assignmentId: {}, studentId: {}, version: {}",
  540. engagement.getAssignmentId(), engagement.getStudentId(), engagement.getVersion());
  541. }
  542. private AssignmentStatus calculateStatus(Date startTime, Date endTime) {
  543. Date now = new Date();
  544. if (now.before(startTime)) {
  545. return AssignmentStatus.NOT_STARTED;
  546. } else if (now.after(endTime)) {
  547. return AssignmentStatus.FINISHED;
  548. } else {
  549. return AssignmentStatus.PROCEEDING;
  550. }
  551. }
  552. @Override
  553. public List<StudentAssignmentStatusVO> getStudentsAssignmentStatus(Long classId, Long assignmentId) {
  554. // 1. 验证班级和作业存在性
  555. Assignment assignment = assignmentMapper.selectById(assignmentId);
  556. if (assignment == null) {
  557. throw new RuntimeException("作业不存在"); // 建议替换为项目自定义异常
  558. }
  559. // 2. 获取班级所有学生(从class_student表)
  560. List<StudentInfoVO> classStudents = classService.getStudentsByClassId(classId);
  561. if (classStudents.isEmpty()) {
  562. return Collections.emptyList();
  563. }
  564. // 3. 提取学号列表,查询对应的用户ID(users表)
  565. List<String> officialNumbers = classStudents.stream()
  566. .map(StudentInfoVO::getOfficialNumber)
  567. .collect(Collectors.toList());
  568. List<User> users = userMapper.selectUserIdByOfficialNumbers(officialNumbers);
  569. Map<String, Long> officialNumberToUserIdMap = users.stream()
  570. .collect(Collectors.toMap(
  571. User::getOfficialNumber,
  572. User::getId
  573. ));
  574. // 4. 查询这些学生的作业提交情况(student_assignment表)
  575. List<Long> studentIds = new ArrayList<>(officialNumberToUserIdMap.values());
  576. List<StudentAssignment> studentAssignments = studentAssignmentMapper.findByAssignmentIdAndStudentIds(assignmentId, studentIds);
  577. // 5. 构建学生ID到作业情况的映射
  578. Map<Long, StudentAssignment> studentIdToAssignmentMap = studentAssignments.stream()
  579. .collect(Collectors.toMap(
  580. StudentAssignment::getStudentId,
  581. Function.identity()
  582. ));
  583. // 6. 组装返回结果
  584. return classStudents.stream()
  585. .map(student -> {
  586. StudentAssignmentStatusVO vo = new StudentAssignmentStatusVO();
  587. vo.setOfficialNumber(student.getOfficialNumber());
  588. vo.setStuName(student.getStuName());
  589. // 设置学生ID
  590. Long studentId = officialNumberToUserIdMap.get(student.getOfficialNumber());
  591. vo.setStudentId(studentId);
  592. // 设置状态和成绩
  593. StudentAssignment studentAssignment = studentIdToAssignmentMap.get(studentId);
  594. if (studentAssignment == null) {
  595. // 未参与作业,按未提交处理
  596. vo.setStatus(AssignmentCompletionStatus.NOT_SUBMITTED);
  597. vo.setScore(0);
  598. } else {
  599. // 转换状态为枚举
  600. vo.setStatus(AssignmentCompletionStatus.fromValue(studentAssignment.getStatus()));
  601. // 处理成绩(BigDecimal转Integer,未批改为0)
  602. vo.setScore(studentAssignment.getScore() != null ?
  603. studentAssignment.getScore().intValue() : 0);
  604. }
  605. return vo;
  606. })
  607. .collect(Collectors.toList());
  608. }
  609. }