Prechádzať zdrojové kódy

update content-service/exam

reghao 7 mesiacov pred
rodič
commit
46f2390aa1
14 zmenil súbory, kde vykonal 537 pridanie a 624 odobranie
  1. 9 4
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/controller/ExamController.java
  2. 32 30
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/controller/ExamResultController.java
  3. 43 10
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/controller/MarkController.java
  4. 6 9
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/controller/PaperController.java
  5. 12 1
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/controller/SubjectController.java
  6. 0 25
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/model/vo/ExamInfo.java
  7. 0 23
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/model/vo/ExamScore.java
  8. 0 17
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/model/vo/ExamStartInfo.java
  9. 0 235
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/service/ExamResultService.java
  10. 17 2
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/service/ExamService.java
  11. 92 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/service/MarkService.java
  12. 4 267
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/service/PaperService.java
  13. 321 0
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/service/PaperViewService.java
  14. 1 1
      content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/service/SubjectService.java

+ 9 - 4
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/controller/ExamController.java

@@ -10,6 +10,7 @@ import cn.reghao.tnb.content.app.exam.model.vo.*;
 import cn.reghao.tnb.content.app.exam.service.ExamService;
 import cn.reghao.tnb.content.app.exam.service.ExamUserService;
 import cn.reghao.tnb.content.app.exam.service.PaperService;
+import cn.reghao.tnb.content.app.exam.service.PaperViewService;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
 import org.springframework.http.MediaType;
@@ -29,11 +30,14 @@ public class ExamController {
     private final ExamUserService examUserService;
     private final ExamService examService;
     private final PaperService paperService;
+    private final PaperViewService paperViewService;
 
-    public ExamController(ExamUserService examUserService, ExamService examService, PaperService paperService) {
+    public ExamController(ExamUserService examUserService, ExamService examService,
+                          PaperService paperService, PaperViewService paperViewService) {
         this.examUserService = examUserService;
         this.examService = examService;
         this.paperService = paperService;
+        this.paperViewService = paperViewService;
     }
 
     @Operation(summary = "获取考试列表", description = "N")
@@ -50,8 +54,9 @@ public class ExamController {
         int viewType = PaperViewType.PaperExam.getCode();
         long userId = UserContext.getUser();
 
-        PaperDetail paperDetail = paperService.getPaperDetail(paperId, viewType, userId);
-        return paperDetail != null ? WebResult.success(paperDetail) : WebResult.notFound();
+        int resultId = 0;
+        PaperDetail paperDetail = paperViewService.getPaperDetail(paperId, viewType, resultId);
+        return WebResult.success(paperDetail);
     }
 
     @Operation(summary = "获取试卷中的试题", description = "N")
@@ -64,7 +69,7 @@ public class ExamController {
     @Operation(summary = "暂存用户提交的试卷答案", description = "N")
     @PostMapping(value = "/cache", produces = MediaType.APPLICATION_JSON_VALUE)
     public String cacheExamPaper(@Validated @RequestBody UserResult userResult) {
-        paperService.cacheUserAnswers(userResult);
+        examService.cacheUserAnswers(userResult);
         return WebResult.success();
     }
 

+ 32 - 30
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/controller/ExamResultController.java

@@ -1,17 +1,15 @@
 package cn.reghao.tnb.content.app.exam.controller;
 
-import cn.reghao.jutil.jdk.db.PageList;
 import cn.reghao.jutil.web.WebResult;
-import cn.reghao.tnb.content.app.exam.model.dto.UserResult;
-import cn.reghao.tnb.content.app.exam.model.query.ExamQuery;
-import cn.reghao.tnb.content.app.exam.model.vo.ExamMark;
-import cn.reghao.tnb.content.app.exam.model.vo.ExamResult;
-import cn.reghao.tnb.content.app.exam.model.vo.ExamScore;
-import cn.reghao.tnb.content.app.exam.service.ExamResultService;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.content.app.exam.model.constant.PaperViewType;
+import cn.reghao.tnb.content.app.exam.model.po.PaperResult;
+import cn.reghao.tnb.content.app.exam.model.vo.PaperDetail;
+import cn.reghao.tnb.content.app.exam.service.PaperService;
+import cn.reghao.tnb.content.app.exam.service.PaperViewService;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
 import org.springframework.http.MediaType;
-import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 /**
@@ -22,37 +20,41 @@ import org.springframework.web.bind.annotation.*;
 @RestController
 @RequestMapping("/api/content/exam/result")
 public class ExamResultController {
-    private final ExamResultService examResultService;
+    private final PaperService paperService;
+    private final PaperViewService paperViewService;
 
-    public ExamResultController(ExamResultService examResultService) {
-        this.examResultService = examResultService;
+    public ExamResultController(PaperService paperService, PaperViewService paperViewService) {
+        this.paperService = paperService;
+        this.paperViewService = paperViewService;
     }
 
     @Operation(summary = "获取试卷结果", description = "N")
-    @GetMapping(value = "/mark", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String getExamMark(ExamQuery examQuery) {
-        PageList<ExamMark> pageList = examResultService.getExamReviewList(examQuery);
-        return WebResult.success(pageList);
-    }
+    @GetMapping(value = "/view/{paperId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getExamMark(@PathVariable("paperId") Integer paperId) {
+        int viewType = PaperViewType.PaperResult.getCode();
+        long loginUser = UserContext.getUser();
 
-    @Operation(summary = "提交评卷结果", description = "N")
-    @PostMapping(value = "/mark", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String submitExamMark(@RequestBody @Validated UserResult userResult) {
-        examResultService.markExamResult(userResult);
-        return WebResult.success();
+        PaperResult paperResult = paperService.getPaperResult(paperId, loginUser);
+        if (paperResult == null) {
+            // 试卷结果不存在
+            PaperDetail paperDetail = new PaperDetail(paperId, 5);
+            return WebResult.success(paperDetail);
+        }
+
+        if (!paperResult.getMarked()) {
+            // 试卷尚未批改
+            PaperDetail paperDetail = new PaperDetail(paperId, 6);
+            return WebResult.success(paperDetail);
+        }
+
+        int resultId = paperResult.getId();
+        PaperDetail paperDetail = paperViewService.getPaperDetail(paperId, viewType, resultId);
+        return WebResult.success(paperDetail);
     }
 
     @Operation(summary = "获取用户的考试成绩", description = "N")
     @GetMapping(value = "/score", produces = MediaType.APPLICATION_JSON_VALUE)
     public String getExamScore() {
-        PageList<ExamScore> pageList = examResultService.getExamScoreList();
-        return WebResult.success(pageList);
-    }
-
-    @Operation(summary = "获取用户的考试结果", description = "N")
-    @GetMapping(value = "/view/{resultId}", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String getExamResult(@PathVariable("resultId") int resultId) {
-        ExamResult examResult = examResultService.getExamResult(resultId);
-        return WebResult.success(examResult);
+        return WebResult.success();
     }
 }

+ 43 - 10
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/controller/MarkController.java

@@ -1,12 +1,17 @@
 package cn.reghao.tnb.content.app.exam.controller;
 
+import cn.reghao.jutil.jdk.db.PageList;
 import cn.reghao.jutil.web.WebResult;
 import cn.reghao.tnb.common.auth.UserContext;
 import cn.reghao.tnb.content.app.exam.model.constant.PaperViewType;
 import cn.reghao.tnb.content.app.exam.model.dto.UserResult;
+import cn.reghao.tnb.content.app.exam.model.po.PaperResult;
+import cn.reghao.tnb.content.app.exam.model.query.ExamQuery;
+import cn.reghao.tnb.content.app.exam.model.vo.ExamMark;
 import cn.reghao.tnb.content.app.exam.model.vo.PaperDetail;
-import cn.reghao.tnb.content.app.exam.service.ExamResultService;
+import cn.reghao.tnb.content.app.exam.service.MarkService;
 import cn.reghao.tnb.content.app.exam.service.PaperService;
+import cn.reghao.tnb.content.app.exam.service.PaperViewService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.http.MediaType;
@@ -21,28 +26,56 @@ import org.springframework.web.bind.annotation.*;
 @RestController
 @RequestMapping("/api/content/exam/mark")
 public class MarkController {
-    private final PaperService paperService;
-    private final ExamResultService examResultService;
+    private PaperService paperService;
+    private final PaperViewService paperViewService;
+    private final MarkService markService;
 
-    public MarkController(PaperService paperService, ExamResultService examResultService) {
+    public MarkController(PaperService paperService, PaperViewService paperViewService, MarkService markService) {
         this.paperService = paperService;
-        this.examResultService = examResultService;
+        this.paperViewService = paperViewService;
+        this.markService = markService;
+    }
+
+    @Operation(summary = "获取阅卷列表", description = "N")
+    @GetMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getExamMark(ExamQuery examQuery) {
+        PageList<ExamMark> pageList = markService.getExamReviewList(examQuery);
+        return WebResult.success(pageList);
     }
 
     @Operation(summary = "获取阅卷视图", description = "N")
     @GetMapping(value = "/view", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String getMarkPaperView(int paperId) {
+    public String getMarkPaperView(@RequestParam("paperId") int paperId,
+                                   @RequestParam("userId") long userId) {
         int viewType = PaperViewType.PaperMark.getCode();
-        long userId = UserContext.getUser();
+        long loginUser = UserContext.getUser();
+        if (userId == 0) {
+            userId = loginUser;
+        }
+
+        PaperResult paperResult = paperService.getPaperResult(paperId, userId);
+        if (paperResult == null) {
+            // 试卷结果不存在
+            PaperDetail paperDetail = new PaperDetail(paperId, 5);
+            return WebResult.success(paperDetail);
+        }
+
+        boolean marked = paperResult.getMarked();
+        if (marked) {
+            // 试卷已批改
+            PaperDetail paperDetail = new PaperDetail(paperId, 7);
+            return WebResult.success(paperDetail);
+        }
 
-        PaperDetail paperDetail = paperService.getPaperDetail(paperId, viewType, userId);
-        return paperDetail != null ? WebResult.success(paperDetail) : WebResult.notFound();
+        int resultId = paperResult.getId();
+        PaperDetail paperDetail = paperViewService.getPaperDetail(paperId, viewType, resultId);
+        return WebResult.success(paperDetail);
     }
 
     @Operation(summary = "提交阅卷结果", description = "N")
     @PostMapping(value = "/submit", produces = MediaType.APPLICATION_JSON_VALUE)
     public String submitExamMark(@RequestBody @Validated UserResult userResult) {
-        examResultService.markExamResult(userResult);
+        markService.markExamResult(userResult);
         return WebResult.success();
     }
 }

+ 6 - 9
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/controller/PaperController.java

@@ -11,6 +11,7 @@ import cn.reghao.tnb.content.app.exam.model.query.PaperQuery;
 import cn.reghao.tnb.content.app.exam.model.vo.PaperDetail;
 import cn.reghao.tnb.content.app.exam.model.vo.PaperView;
 import cn.reghao.tnb.content.app.exam.service.PaperService;
+import cn.reghao.tnb.content.app.exam.service.PaperViewService;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
 import org.springframework.http.MediaType;
@@ -29,9 +30,11 @@ import java.util.List;
 @RequestMapping("/api/content/exam/paper")
 public class PaperController {
     private final PaperService paperService;
+    private final PaperViewService paperViewService;
 
-    public PaperController(PaperService paperService) {
+    public PaperController(PaperService paperService, PaperViewService paperViewService) {
         this.paperService = paperService;
+        this.paperViewService = paperViewService;
     }
 
     @AuthUser
@@ -65,15 +68,9 @@ public class PaperController {
         int paperId = getPaperQuery.getPaperId();
         int viewType = getPaperQuery.getViewType();
         Long userId = getPaperQuery.getUserId();
-        if (userId == null) {
-            userId = -1L;
-        }
-
-        PaperDetail paperDetail = paperService.getPaperDetail(paperId, viewType, userId);
-        if (paperDetail == null) {
-            return WebResult.notFound();
-        }
 
+        int resultId = 0;
+        PaperDetail paperDetail = paperViewService.getPaperDetail(paperId, viewType, resultId);
         return WebResult.success(paperDetail);
     }
 

+ 12 - 1
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/controller/SubjectController.java

@@ -3,8 +3,10 @@ package cn.reghao.tnb.content.app.exam.controller;
 import cn.reghao.jutil.jdk.db.PageList;
 import cn.reghao.jutil.web.WebResult;
 import cn.reghao.tnb.common.db.KeyValue;
+import cn.reghao.tnb.content.app.geo.model.vo.CascadeOption;
 import cn.reghao.tnb.content.app.exam.model.vo.SubjectInfo;
 import cn.reghao.tnb.content.app.exam.service.SubjectService;
+import cn.reghao.tnb.content.app.geo.service.MapService;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
 import org.springframework.http.MediaType;
@@ -24,9 +26,11 @@ import java.util.List;
 @RequestMapping("/api/content/exam/subject")
 public class SubjectController {
     private final SubjectService subjectService;
+    private final MapService mapService;
 
-    public SubjectController(SubjectService subjectService) {
+    public SubjectController(SubjectService subjectService, MapService mapService) {
         this.subjectService = subjectService;
+        this.mapService = mapService;
     }
 
     @Operation(summary = "获取科目列表", description = "N")
@@ -45,4 +49,11 @@ public class SubjectController {
         List<KeyValue> list = subjectService.getKeyValues();
         return WebResult.success(list);
     }
+
+    @Operation(summary = "获取三级联动列表", description = "N")
+    @GetMapping(value = "/cascade", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getLabelValues() {
+        List<CascadeOption> list = mapService.getChinaCascade();
+        return WebResult.success(list);
+    }
 }

+ 0 - 25
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/model/vo/ExamInfo.java

@@ -1,25 +0,0 @@
-package cn.reghao.tnb.content.app.exam.model.vo;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-/**
- * @author reghao
- * @date 2024-08-24 15:17:40
- */
-@AllArgsConstructor
-@Getter
-public class ExamInfo {
-    private long examId;
-    private String examName;
-    private String examDesc;
-    private int type;
-    private String password;
-    private int duration;
-    private String startTime;
-    private String endTime;
-    private int totalScore;
-    private int passScore;
-    private int status;
-    private String questionIds;
-}

+ 0 - 23
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/model/vo/ExamScore.java

@@ -1,23 +0,0 @@
-package cn.reghao.tnb.content.app.exam.model.vo;
-
-/**
- * @author reghao
- * @date 2024-08-29 11:33:34
- */
-public class ExamScore {
-    private int resultId;
-    private int paperId;
-    private String name;
-    private String examTime;
-    private int totalScore;
-    private int userScore;
-
-    public ExamScore(ExamResults examResults, String student) {
-        this.resultId = examResults.getResultId();
-        this.paperId = examResults.getPaperId();
-        this.name = examResults.getName();
-        this.examTime = examResults.getExamTime();
-        this.totalScore = examResults.getTotalScore();
-        this.userScore = examResults.getUserScore();
-    }
-}

+ 0 - 17
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/model/vo/ExamStartInfo.java

@@ -1,17 +0,0 @@
-package cn.reghao.tnb.content.app.exam.model.vo;
-
-import cn.reghao.tnb.content.app.exam.model.po.Paper;
-
-/**
- * @author reghao
- * @date 2024-08-28 22:28:29
- */
-public class ExamStartInfo {
-    private Integer examId;
-    private Integer examDuration;
-
-    public ExamStartInfo(Paper paper) {
-        this.examId = paper.getId();
-        this.examDuration = paper.getDuration();
-    }
-}

+ 0 - 235
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/service/ExamResultService.java

@@ -1,235 +0,0 @@
-package cn.reghao.tnb.content.app.exam.service;
-
-import cn.reghao.jutil.jdk.db.Page;
-import cn.reghao.jutil.jdk.db.PageList;
-import cn.reghao.tnb.account.api.dto.AccountInfo;
-import cn.reghao.tnb.account.api.iface.AccountQuery;
-import cn.reghao.tnb.common.auth.UserContext;
-import cn.reghao.tnb.content.app.exam.db.mapper.*;
-import cn.reghao.tnb.content.app.exam.model.constant.QuestionType;
-import cn.reghao.tnb.content.app.exam.model.dto.UserResult;
-import cn.reghao.tnb.content.app.exam.model.po.*;
-import cn.reghao.tnb.content.app.exam.model.query.ExamQuery;
-import cn.reghao.tnb.content.app.exam.model.vo.*;
-import org.apache.dubbo.config.annotation.DubboReference;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.*;
-import java.util.stream.Collectors;
-
-/**
- * @author reghao
- * @date 2024-08-29 09:55:20
- */
-@Service
-public class ExamResultService {
-    @DubboReference(check = false, retries = 0)
-    private AccountQuery accountQuery;
-
-    private final PaperMapper paperMapper;
-    private final PaperQuestionMapper paperQuestionMapper;
-    private final PaperResultMapper paperResultMapper;
-    private final PaperAnswerMapper paperAnswerMapper;
-    private final QuestionMapper questionMapper;
-    private final QuestionOptionMapper questionOptionMapper;
-    private final PaperService paperService;
-
-    public ExamResultService(PaperMapper paperMapper, PaperQuestionMapper paperQuestionMapper,
-                             PaperResultMapper paperResultMapper, PaperAnswerMapper paperAnswerMapper,
-                             QuestionMapper questionMapper, QuestionOptionMapper questionOptionMapper,
-                             PaperService paperService) {
-        this.paperMapper = paperMapper;
-        this.paperQuestionMapper = paperQuestionMapper;
-        this.paperResultMapper = paperResultMapper;
-        this.paperAnswerMapper = paperAnswerMapper;
-        this.questionMapper = questionMapper;
-        this.questionOptionMapper = questionOptionMapper;
-        this.paperService = paperService;
-    }
-
-    public PageList<ExamMark> getExamReviewList(ExamQuery examQuery) {
-        int total = paperResultMapper.countByCriteria(examQuery);
-        Page page = examQuery.getPage();
-        List<ExamResults> list = paperResultMapper.findExamResultsByPage(page, examQuery);
-        List<ExamMark> list1 = list.stream()
-                .map(examResults -> {
-                    long userId = examResults.getStudent();
-                    String student = ""+userId;
-                    AccountInfo accountInfo = accountQuery.getAccountInfo(userId);
-                    if (accountInfo != null) {
-                        student = accountInfo.getScreenName();
-                    }
-                    return new ExamMark(examResults, student, userId);
-                })
-                .collect(Collectors.toList());
-        return PageList.pageList(page, total, list1);
-    }
-
-    @Transactional(rollbackFor = Exception.class)
-    public void markExamResult(UserResult userResult) {
-        int paperId = userResult.getPaperId();
-        long loginUser = UserContext.getUser();
-        PaperResult paperResult = getPaperResult(paperId, loginUser);
-        if (paperResult == null) {
-            return;
-        }
-
-        int totalSubjectiveScore = 0;
-        int resultId = paperResult.getId();
-        for (QuestionAnswer questionAnswer : userResult.getSubmitUserAnswers()) {
-            long questionId = questionAnswer.getQuestionId();
-            int questionType = questionAnswer.getQuestionType();
-            if (questionType <= QuestionType.QUESTION3.getCode()) {
-                continue;
-            }
-
-            String review = "";
-            int submitScore = questionAnswer.getSubmitScore();
-            paperAnswerMapper.updatePaperAnswer(resultId, questionId, review, submitScore);
-            totalSubjectiveScore += submitScore;
-        }
-
-        paperResultMapper.updateSetResultMarked(resultId, totalSubjectiveScore);
-    }
-
-    private PaperResult getPaperResult(int paperId, long userId) {
-        ExamQuery examQuery = new ExamQuery.Builder()
-                .loginUser(userId)
-                .paperId(paperId)
-                .build();
-        Page page = examQuery.getPage();
-        List<PaperResult> list = paperResultMapper.findPaperResultByPage(page, examQuery);
-        return list.isEmpty() ? null : list.get(0);
-    }
-
-    public PageList<ExamScore> getExamScoreList() {
-        ExamQuery examQuery = new ExamQuery.Builder().marked(true).build();
-        int total = paperResultMapper.countByCriteria(examQuery);
-        Page page = examQuery.getPage();
-        List<ExamResults> list = paperResultMapper.findExamResultsByPage(page, examQuery);
-        List<ExamScore> list1 = list.stream()
-                .map(examResults -> {
-                    long userId = examResults.getStudent();
-                    AccountInfo accountInfo = accountQuery.getAccountInfo(userId);
-                    String student = ""+userId;
-                    if (accountInfo != null) {
-                        student = accountInfo.getScreenName();
-                    }
-                    return new ExamScore(examResults, student);
-                })
-                .collect(Collectors.toList());
-        return PageList.pageList(page, total, list1);
-    }
-
-    public ExamResult getExamResult(int resultId) {
-        PaperResult paperResult = paperResultMapper.findById(resultId);
-        if (paperResult.getMarked()) {
-            return null;
-        }
-
-        List<PaperAnswer> paperAnswers = paperAnswerMapper.findByResultId(resultId);
-        Map<Long, PaperAnswer> answerMap = paperAnswers.stream()
-                .collect(Collectors.toMap(PaperAnswer::getQuestionId, paperAnswer -> paperAnswer));
-
-        int paperId = paperResult.getPaperId();
-        Paper paper = paperMapper.findById(paperId);
-        List<PaperQuestion> paperQuestions = paperQuestionMapper.findByPaperId(paperId);
-        paperQuestions.sort(Comparator.comparingInt(PaperQuestion::getPos));
-
-        List<Long> questionIds = paperQuestions.stream()
-                .map(PaperQuestion::getQuestionId)
-                .collect(Collectors.toList());
-        List<QuestionInfo> questionInfos = paperService.getPaperQuestions(paperId);
-        List<QuestionInfo> list = new ArrayList<>();
-        for (QuestionInfo questionInfo : questionInfos) {
-            List<QuestionInfo> children = questionInfo.getChildren();
-            if (children != null) {
-                list.addAll(children);
-            }
-        }
-
-        if (!list.isEmpty()) {
-            questionInfos.addAll(list);
-        }
-
-        List<QuestionResult> questionResults = getQuestionResults(questionIds, questionInfos, answerMap);
-        return new ExamResult(paper, paperResult, questionResults);
-    }
-
-    private List<QuestionResult> getQuestionResults(List<Long> questionIds,
-                                                   List<QuestionInfo> questionInfos,
-                                                   Map<Long, PaperAnswer> answerMap) {
-        Map<Long, QuestionInfo> questionMap = questionInfos.stream()
-                .collect(Collectors.toMap(QuestionInfo::getQuestionId, k -> k));
-
-        List<Question> questions = questionMapper.findByQuestionIds(questionIds);
-        return questions.stream().map(question -> {
-            long questionId = question.getQuestionId();
-            boolean child = question.getChild();
-            QuestionResult questionResult;
-            if (child) {
-                List<QuestionResult> children = questionMapper.findByPid(questionId).stream()
-                        .map(question1 -> getQuestionResult(question1, questionMap, answerMap))
-                        .collect(Collectors.toList());
-                int userScore = children.stream()
-                        .map(QuestionResult::getUserScore)
-                        .mapToInt(Integer::intValue)
-                        .sum();
-
-                questionResult = new QuestionResult();
-                QuestionInfo questionInfo = questionMap.get(questionId);
-                questionResult.setQuestionInfo(questionInfo);
-                questionResult.setChildren(children);
-                questionResult.setUserScore(userScore);
-            } else {
-                questionResult = getQuestionResult(question, questionMap, answerMap);
-            }
-            return questionResult;
-        }).collect(Collectors.toList());
-    }
-
-    private QuestionResult getQuestionResult(Question question,
-                                             Map<Long, QuestionInfo> questionMap,
-                                             Map<Long, PaperAnswer> answerMap) {
-        long questionId = question.getQuestionId();
-        List<QuestionOption> questionOptions = questionOptionMapper.findByQuestionId(questionId);
-        List<QuestionOption> correctOptions = questionOptions.stream()
-                .filter(QuestionOption::getCorrect)
-                .collect(Collectors.toList());
-
-        QuestionResult questionResult = new QuestionResult();
-        PaperAnswer paperAnswer = answerMap.get(questionId);
-        String answer = paperAnswer.getAnswer();
-        int type = question.getType();
-        if (type == QuestionType.QUESTION1.getCode() || type == QuestionType.QUESTION4.getCode()) {
-            int userPos = Integer.parseInt(answer);
-            int correctPos = correctOptions.get(0).getPos();
-            questionResult.setUserAnswer(userPos);
-            questionResult.setCorrectAnswer(correctPos);
-            questionResult.setUserScore(paperAnswer.getScore());
-        } else if (type == QuestionType.QUESTION2.getCode() || type == QuestionType.QUESTION3.getCode()) {
-            List<Integer> userPosList = Arrays.stream(answer.split(","))
-                    .map(Integer::parseInt)
-                    .collect(Collectors.toList());
-            List<Integer> correctPosList = correctOptions.stream()
-                    .map(QuestionOption::getPos)
-                    .collect(Collectors.toList());
-            questionResult.setUserAnswer(userPosList);
-            questionResult.setCorrectAnswer(correctPosList);
-            questionResult.setUserScore(paperAnswer.getScore());
-        } else if (type == QuestionType.QUESTION5.getCode() || type == QuestionType.QUESTION6.getCode()) {
-            // 填空题
-            // 问答题
-            questionResult.setUserAnswer(answer);
-            String correctAnswer = correctOptions.get(0).getContent();
-            questionResult.setCorrectAnswer(correctAnswer);
-            questionResult.setUserScore(paperAnswer.getScore());
-        } else {
-        }
-
-        QuestionInfo questionInfo = questionMap.get(questionId);
-        questionResult.setQuestionInfo(questionInfo);
-        return questionResult;
-    }
-}

+ 17 - 2
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/service/ExamService.java

@@ -6,6 +6,7 @@ import cn.reghao.tnb.content.app.exam.model.constant.QuestionType;
 import cn.reghao.tnb.content.app.exam.model.dto.UserResult;
 import cn.reghao.tnb.content.app.exam.model.po.*;
 import cn.reghao.tnb.content.app.exam.model.vo.QuestionAnswer;
+import cn.reghao.tnb.content.app.util.redis.ds.RedisHash;
 import cn.reghao.tnb.content.app.util.redis.ds.RedisOps;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -19,24 +20,39 @@ import java.util.stream.Collectors;
  */
 @Service
 public class ExamService {
+    private String keyPrefix = "tnb:exam:result";
     private final QuestionMapper questionMapper;
     private final QuestionOptionMapper questionOptionMapper;
     private final PaperQuestionMapper paperQuestionMapper;
     private final PaperResultMapper paperResultMapper;
     private final PaperAnswerMapper paperAnswerMapper;
+    private RedisHash redisHash;
     private RedisOps redisOps;
 
     public ExamService(QuestionMapper questionMapper, QuestionOptionMapper questionOptionMapper,
                        PaperQuestionMapper paperQuestionMapper, PaperResultMapper paperResultMapper,
-                       PaperAnswerMapper paperAnswerMapper, RedisOps redisOps) {
+                       PaperAnswerMapper paperAnswerMapper, RedisHash redisHash, RedisOps redisOps) {
         this.questionMapper = questionMapper;
         this.questionOptionMapper = questionOptionMapper;
         this.paperQuestionMapper = paperQuestionMapper;
         this.paperResultMapper = paperResultMapper;
         this.paperAnswerMapper = paperAnswerMapper;
+        this.redisHash = redisHash;
         this.redisOps = redisOps;
     }
 
+    public void cacheUserAnswers(UserResult userResult) {
+        long loginUser = UserContext.getUser();
+        int paperId = userResult.getPaperId();
+        String key = String.format("%s:%s:%s", keyPrefix, paperId, loginUser);
+
+        List<QuestionAnswer> submitUserAnswers = userResult.getSubmitUserAnswers();
+        for (QuestionAnswer userAnswer : submitUserAnswers) {
+            long questionId = userAnswer.getQuestionId();
+            redisHash.hset(key, ""+questionId, userAnswer);
+        }
+    }
+
     public int submitExamPaper(UserResult userResult) {
         int paperId = userResult.getPaperId();
         List<PaperQuestion> paperQuestions = paperQuestionMapper.findByPaperId(paperId);
@@ -80,7 +96,6 @@ public class ExamService {
         int resultId = savePaperResult(paperResult, paperAnswers);
 
         long loginUser = UserContext.getUser();
-        String keyPrefix = "tnb:exam:result";
         String key = String.format("%s:%s:%s", keyPrefix, paperId, loginUser);
         redisOps.del(key);
         return resultId;

+ 92 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/service/MarkService.java

@@ -0,0 +1,92 @@
+package cn.reghao.tnb.content.app.exam.service;
+
+import cn.reghao.jutil.jdk.db.Page;
+import cn.reghao.jutil.jdk.db.PageList;
+import cn.reghao.tnb.account.api.dto.AccountInfo;
+import cn.reghao.tnb.account.api.iface.AccountQuery;
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.content.app.exam.db.mapper.*;
+import cn.reghao.tnb.content.app.exam.model.constant.QuestionType;
+import cn.reghao.tnb.content.app.exam.model.dto.UserResult;
+import cn.reghao.tnb.content.app.exam.model.po.*;
+import cn.reghao.tnb.content.app.exam.model.query.ExamQuery;
+import cn.reghao.tnb.content.app.exam.model.vo.*;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2024-08-29 09:55:20
+ */
+@Service
+public class MarkService {
+    @DubboReference(check = false, retries = 0)
+    private AccountQuery accountQuery;
+
+    private final PaperResultMapper paperResultMapper;
+    private final PaperAnswerMapper paperAnswerMapper;
+
+    public MarkService(PaperResultMapper paperResultMapper, PaperAnswerMapper paperAnswerMapper) {
+        this.paperResultMapper = paperResultMapper;
+        this.paperAnswerMapper = paperAnswerMapper;
+    }
+
+    public PageList<ExamMark> getExamReviewList(ExamQuery examQuery) {
+        int total = paperResultMapper.countByCriteria(examQuery);
+        Page page = examQuery.getPage();
+        List<ExamResults> list = paperResultMapper.findExamResultsByPage(page, examQuery);
+        List<ExamMark> list1 = list.stream()
+                .map(examResults -> {
+                    long userId = examResults.getStudent();
+                    String student = ""+userId;
+                    AccountInfo accountInfo = accountQuery.getAccountInfo(userId);
+                    if (accountInfo != null) {
+                        student = accountInfo.getScreenName();
+                    }
+                    return new ExamMark(examResults, student, userId);
+                })
+                .collect(Collectors.toList());
+        return PageList.pageList(page, total, list1);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void markExamResult(UserResult userResult) {
+        int paperId = userResult.getPaperId();
+        long loginUser = UserContext.getUser();
+        PaperResult paperResult = getPaperResult(paperId, loginUser);
+        if (paperResult == null) {
+            return;
+        }
+
+        int totalSubjectiveScore = 0;
+        int resultId = paperResult.getId();
+        for (QuestionAnswer questionAnswer : userResult.getSubmitUserAnswers()) {
+            long questionId = questionAnswer.getQuestionId();
+            int questionType = questionAnswer.getQuestionType();
+            if (questionType <= QuestionType.QUESTION3.getCode()) {
+                continue;
+            }
+
+            String review = "";
+            int submitScore = questionAnswer.getSubmitScore();
+            paperAnswerMapper.updatePaperAnswer(resultId, questionId, review, submitScore);
+            totalSubjectiveScore += submitScore;
+        }
+
+        paperResultMapper.updateSetResultMarked(resultId, totalSubjectiveScore);
+    }
+
+    private PaperResult getPaperResult(int paperId, long userId) {
+        ExamQuery examQuery = new ExamQuery.Builder()
+                .loginUser(userId)
+                .paperId(paperId)
+                .build();
+        Page page = examQuery.getPage();
+        List<PaperResult> list = paperResultMapper.findPaperResultByPage(page, examQuery);
+        return list.isEmpty() ? null : list.get(0);
+    }
+}

+ 4 - 267
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/service/PaperService.java

@@ -6,15 +6,11 @@ import cn.reghao.jutil.jdk.result.Result;
 import cn.reghao.tnb.common.auth.UserContext;
 import cn.reghao.tnb.common.db.KeyValue;
 import cn.reghao.tnb.content.app.exam.db.mapper.*;
-import cn.reghao.tnb.content.app.exam.model.constant.PaperViewType;
-import cn.reghao.tnb.content.app.exam.model.constant.QuestionType;
 import cn.reghao.tnb.content.app.exam.model.dto.PaperAddDto;
-import cn.reghao.tnb.content.app.exam.model.dto.UserResult;
 import cn.reghao.tnb.content.app.exam.model.po.*;
 import cn.reghao.tnb.content.app.exam.model.query.ExamQuery;
 import cn.reghao.tnb.content.app.exam.model.query.PaperQuery;
 import cn.reghao.tnb.content.app.exam.model.vo.*;
-import cn.reghao.tnb.content.app.util.redis.ds.RedisHash;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -33,23 +29,17 @@ public class PaperService {
     private final PaperQuestionMapper paperQuestionMapper;
     private final QuestionMapper questionMapper;
     private final QuestionOptionMapper questionOptionMapper;
-    private final RedisHash redisHash;
-    private String keyPrefix = "tnb:exam:result";
-    private PaperResultMapper paperResultMapper;
-    private PaperAnswerMapper paperAnswerMapper;
-    private ExamUserMapper examUserMapper;
+    private final PaperResultMapper paperResultMapper;
+    private final ExamUserMapper examUserMapper;
 
     public PaperService(PaperMapper paperMapper, PaperQuestionMapper paperQuestionMapper,
                         QuestionMapper questionMapper, QuestionOptionMapper questionOptionMapper,
-                        RedisHash redisHash, PaperResultMapper paperResultMapper,
-                        PaperAnswerMapper paperAnswerMapper, ExamUserMapper examUserMapper) {
+                        PaperResultMapper paperResultMapper, ExamUserMapper examUserMapper) {
         this.paperMapper = paperMapper;
         this.paperQuestionMapper = paperQuestionMapper;
         this.questionMapper = questionMapper;
         this.questionOptionMapper = questionOptionMapper;
-        this.redisHash = redisHash;
         this.paperResultMapper = paperResultMapper;
-        this.paperAnswerMapper = paperAnswerMapper;
         this.examUserMapper = examUserMapper;
     }
 
@@ -187,160 +177,7 @@ public class PaperService {
         return questionInfo;
     }
 
-    public PaperDetail getPaperDetail(int paperId, int viewType, long userId) {
-        long loginUser = UserContext.getUser();
-        ExamUser examUser = examUserMapper.findExamUser(paperId, loginUser);
-        if (examUser == null) {
-            // 用户没有访问试卷的权限
-            return new PaperDetail(paperId, 1);
-        }
-
-        Paper paper = paperMapper.findById(paperId);
-        int role = examUser.getRole();
-
-        Map<Long, QuestionAnswer> paperAnswers;
-        if (viewType == PaperViewType.PaperPreview.getCode()) {
-            if (role > 1) {
-                // 用户只有做题的权限
-                return new PaperDetail(paperId, 3);
-            }
-
-            paperAnswers = getPaperAnswersFromCorrect(paperId);
-        } else if (viewType == PaperViewType.PaperExam.getCode()) {
-            if (role == 1) {
-                // 用户只有管理的权限
-                return new PaperDetail(paperId, 2);
-            }
-
-            paperAnswers = getPaperAnswersFromCache(paperId, loginUser);
-        } else if (viewType == PaperViewType.PaperMark.getCode()) {
-            int markType = paper.getMarkType();
-            if (markType == 2) {
-                // 自己批改
-                if (role > 1) {
-                    // 试卷由用户自己批改
-                    return new PaperDetail(paperId, 4);
-                }
-            } else if (markType == 3) {
-                // 他人批改
-                if (role > 1) {
-                    // 用户只有做题的权限
-                    return new PaperDetail(paperId, 3);
-                }
-            }
-
-            // 批改试卷不需要客观题
-            paperAnswers = getPaperAnswersFromResult(paperId, loginUser, false);
-        } else if (viewType == PaperViewType.PaperResult.getCode()) {
-            if (role == 1) {
-                // 用户只有管理的权限
-                return new PaperDetail(paperId, 2);
-            }
-
-            paperAnswers = getPaperAnswersFromResult(paperId, loginUser, true);
-        } else {
-            // 试卷访问类型 viewType 未知
-            return new PaperDetail(paperId, 5);
-        }
-
-        int paperStatus = 6;
-        Set<Integer> questionTypes = paperAnswers.values().stream()
-                .collect(Collectors.groupingBy(QuestionAnswer::getQuestionType))
-                .keySet();
-        Map<Integer, List<QuestionInfo>> questionMap = getPaperQuestions(paperId).stream()
-                .filter(questionInfo -> {
-                    int questionType = questionInfo.getQuestionType();
-                    return questionTypes.contains(questionType);
-                })
-                .collect(Collectors.groupingBy(QuestionInfo::getQuestionType));
-        UserResult userResult = new UserResult(paperId, paperAnswers);
-        return new PaperDetail(paper, paperStatus, questionMap, userResult);
-    }
-
-    public void cacheUserAnswers(UserResult userResult) {
-        long loginUser = UserContext.getUser();
-        int paperId = userResult.getPaperId();
-        String key = String.format("%s:%s:%s", keyPrefix, paperId, loginUser);
-
-        List<QuestionAnswer> submitUserAnswers = userResult.getSubmitUserAnswers();
-        for (QuestionAnswer userAnswer : submitUserAnswers) {
-            long questionId = userAnswer.getQuestionId();
-            redisHash.hset(key, ""+questionId, userAnswer);
-        }
-    }
-
-    private Map<Long, QuestionAnswer> getPaperAnswersFromCache(int paperId, long userId) {
-        Map<Long, QuestionAnswer> map = new HashMap<>();
-        String key = String.format("%s:%s:%s", keyPrefix, paperId, userId);
-        redisHash.hgetall(key).forEach((k, v) -> {
-            QuestionAnswer questionAnswer = (QuestionAnswer) v;
-            long questionId = questionAnswer.getQuestionId();
-            Object[] submitAnswer = questionAnswer.getSubmitAnswer();
-            int questionType = questionAnswer.getQuestionType();
-            if (questionType == QuestionType.QUESTION1.getCode()) {
-                if (submitAnswer[0] instanceof String) {
-                    Object[] submitAnswer1 = new Object[] {Integer.parseInt((String) submitAnswer[0])};
-                    questionAnswer.setSubmitAnswer(submitAnswer1);
-                }
-            }
-            map.put(questionId, questionAnswer);
-        });
-        if (!map.isEmpty()) {
-            return map;
-        }
-
-        return getEmptyPaperAnswers(paperId);
-    }
-
-    /**
-     * 获取空白的试卷答案
-     *
-     * @param
-     * @return
-     * @date 2025-08-10 18:08:71
-     */
-    private Map<Long, QuestionAnswer> getEmptyPaperAnswers(int paperId) {
-        Map<Long, QuestionAnswer> map = new HashMap<>();
-        List<QuestionInfo> questions = getPaperQuestions(paperId);
-        for (QuestionInfo questionInfo : questions) {
-            QuestionAnswer questionAnswer = new QuestionAnswer(questionInfo);
-            long questionId = questionInfo.getQuestionId();
-            int type = questionInfo.getQuestionType();
-            List<QuestionOption> questionOptionList = questionOptionMapper.findByQuestionId(questionId);
-            long count = questionOptionList.stream().filter(QuestionOption::getCorrect).count();
-            List<Object> list = new ArrayList<>();
-            for (int i = 0; i < count; i++) {
-                if (type == QuestionType.QUESTION1.getCode() || type == QuestionType.QUESTION3.getCode()) {
-                    // 单选题和判断题前端使用 radio, 需要 int 类型来表示选中的 radio
-                    list.add(-1);
-                } else if (type == QuestionType.QUESTION2.getCode()) {
-                    // 多选题前端使用 checkbox, 使用 string[] 来表示选中的 checkbox
-                    // 空数组表示没有选中
-                } else {
-                    // 填空题和简答题使用空字符串占位
-                    list.add("");
-                }
-                questionAnswer.setSubmitAnswer(list.toArray(new Object[0]));
-            }
-
-            map.putIfAbsent(questionId, questionAnswer);
-        }
-
-        return map;
-    }
-
-    private Map<Long, QuestionAnswer> getPaperAnswersFromResult(int paperId, long userId, boolean hasObjective) {
-        PaperResult paperResult = getPaperResult(paperId, userId);
-        if (paperResult == null) {
-            log.error("userId:{} 的试卷 paperId:{} 结果不存在", userId, paperId);
-            return getEmptyPaperAnswers(paperId);
-        }
-
-        int resultId = paperResult.getId();
-        return getPaperAnswersFromResult0(paperId, resultId, hasObjective);
-    }
-
-    private PaperResult getPaperResult(int paperId, long userId) {
+    public PaperResult getPaperResult(int paperId, long userId) {
         ExamQuery examQuery = new ExamQuery.Builder()
                 .loginUser(userId)
                 .paperId(paperId)
@@ -350,106 +187,6 @@ public class PaperService {
         return list.isEmpty() ? null : list.get(0);
     }
 
-    private Map<Long, QuestionAnswer> getPaperAnswersFromResult0(int paperId, int resultId, boolean hasObjective) {
-        Map<Long, QuestionAnswer> map = new HashMap<>();
-        List<PaperAnswer> paperAnswerList = paperAnswerMapper.findByResultId(resultId);
-        paperAnswerList.forEach(paperAnswer -> {
-            long questionId = paperAnswer.getQuestionId();
-            int questionType = paperAnswer.getQuestionType();
-            if (questionType <= QuestionType.QUESTION3.getCode() && !hasObjective) {
-                return;
-            }
-
-            PaperQuestion paperQuestion = paperQuestionMapper.findPaperQuestion(paperId, questionId);
-            int correctScore = paperQuestion.getScore();
-            QuestionAnswer questionAnswer = new QuestionAnswer(paperAnswer, correctScore);
-
-            setSubmitAnswer(paperAnswer, questionAnswer);
-            setCorrectAnswer(questionAnswer);
-            map.put(questionId, questionAnswer);
-        });
-
-        return map;
-    }
-
-    private void setCorrectAnswer(QuestionAnswer questionAnswer) {
-        long questionId = questionAnswer.getQuestionId();
-        List<QuestionOption> list = questionOptionMapper.findByQuestionId(questionId);
-        List<Object> correctAnswers = list.stream()
-                .filter(QuestionOption::getCorrect)
-                .map(QuestionOption::getContent)
-                .collect(Collectors.toList());
-        questionAnswer.setCorrectAnswer(correctAnswers.toArray(new Object[0]));
-    }
-
-    private void setSubmitAnswer(PaperAnswer paperAnswer, QuestionAnswer questionAnswer) {
-        int type = paperAnswer.getQuestionType();
-        String submitAnswer = paperAnswer.getAnswer();
-        List<Object> submitAnswerList;
-        if (type == QuestionType.QUESTION1.getCode() || type == QuestionType.QUESTION3.getCode()) {
-            // 单选题和判断题前端使用 radio, 需要 int 类型来表示选中的 radio
-            submitAnswerList = List.of(Integer.parseInt(submitAnswer));
-        } else if (type == QuestionType.QUESTION2.getCode()) {
-            // 多选题前端使用 checkbox, 使用 string[] 来表示选中的 checkbox
-            String[] answerArr = submitAnswer.split(",");
-            submitAnswerList = List.of(answerArr);
-        } else if (type == QuestionType.QUESTION4.getCode()) {
-            // 填空题
-            String[] answerArr = submitAnswer.split(",");
-            submitAnswerList = List.of(answerArr);
-        } else if (type == QuestionType.QUESTION5.getCode()) {
-            // 问答题
-            submitAnswerList = List.of(submitAnswer);
-        } else if (type == QuestionType.QUESTION6.getCode()) {
-            // 组合题
-            submitAnswerList = List.of(submitAnswer);
-        } else {
-            submitAnswerList = List.of(submitAnswer);
-        }
-        questionAnswer.setSubmitAnswer(submitAnswerList.toArray(new Object[0]));
-    }
-
-    private Map<Long, QuestionAnswer> getPaperAnswersFromCorrect(int paperId) {
-        Map<Long, QuestionAnswer> map = new HashMap<>();
-        List<PaperQuestion> paperQuestions = paperQuestionMapper.findByPaperId(paperId);
-        Map<Long, PaperQuestion> paperQuestionMap = paperQuestions.stream()
-                .collect(Collectors.toMap(PaperQuestion::getQuestionId, k -> k));
-        //List<Long> questionIds = paperQuestions.stream().map(PaperQuestion::getQuestionId).collect(Collectors.toList());
-        List<Long> questionIds = new ArrayList<>(paperQuestionMap.keySet());
-
-        List<Question> questions = questionMapper.findByQuestionIds(questionIds);
-        questions.forEach(question -> {
-            long questionId = question.getQuestionId();
-            int type = question.getType();
-            int correctScore = paperQuestionMap.get(questionId).getScore();
-            QuestionAnswer questionAnswer = new QuestionAnswer(question, correctScore);
-
-            List<QuestionOption> list = questionOptionMapper.findByQuestionId(questionId);
-            // 正确答案
-            List<Object> correctAnswerList;
-            if (type == QuestionType.QUESTION1.getCode() || type == QuestionType.QUESTION3.getCode()) {
-                correctAnswerList = list.stream()
-                        .filter(QuestionOption::getCorrect)
-                        .map(QuestionOption::getPos)
-                        .collect(Collectors.toList());
-            } else if (type == QuestionType.QUESTION2.getCode()) {
-                correctAnswerList = list.stream()
-                        .filter(QuestionOption::getCorrect)
-                        .map(QuestionOption::getPos)
-                        .collect(Collectors.toList());
-            } else {
-                correctAnswerList = list.stream()
-                        .filter(QuestionOption::getCorrect)
-                        .map(QuestionOption::getContent)
-                        .collect(Collectors.toList());
-            }
-
-            questionAnswer.setCorrectAnswer(correctAnswerList.toArray(new Object[0]));
-            map.put(questionId, questionAnswer);
-        });
-        return map;
-    }
-
     public List<KeyValue> getKeyValues() {
         PaperQuery paperQuery = new PaperQuery.Builder().pageSize(10).build();
         Page page = paperQuery.getPage();

+ 321 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/service/PaperViewService.java

@@ -0,0 +1,321 @@
+package cn.reghao.tnb.content.app.exam.service;
+
+import cn.reghao.tnb.common.auth.UserContext;
+import cn.reghao.tnb.content.app.exam.db.mapper.*;
+import cn.reghao.tnb.content.app.exam.model.constant.PaperViewType;
+import cn.reghao.tnb.content.app.exam.model.constant.QuestionType;
+import cn.reghao.tnb.content.app.exam.model.dto.UserResult;
+import cn.reghao.tnb.content.app.exam.model.po.*;
+import cn.reghao.tnb.content.app.exam.model.vo.*;
+import cn.reghao.tnb.content.app.util.redis.ds.RedisHash;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2025-08-12 14:48:05
+ */
+@Slf4j
+@Service
+public class PaperViewService {
+    private String keyPrefix = "tnb:exam:result";
+    private final PaperMapper paperMapper;
+    private final PaperQuestionMapper paperQuestionMapper;
+    private final QuestionMapper questionMapper;
+    private final QuestionOptionMapper questionOptionMapper;
+    private final RedisHash redisHash;
+    private PaperAnswerMapper paperAnswerMapper;
+    private ExamUserMapper examUserMapper;
+
+    public PaperViewService(PaperMapper paperMapper, PaperQuestionMapper paperQuestionMapper,
+                            QuestionMapper questionMapper, QuestionOptionMapper questionOptionMapper,
+                            RedisHash redisHash, PaperAnswerMapper paperAnswerMapper, ExamUserMapper examUserMapper) {
+        this.paperMapper = paperMapper;
+        this.paperQuestionMapper = paperQuestionMapper;
+        this.questionMapper = questionMapper;
+        this.questionOptionMapper = questionOptionMapper;
+        this.redisHash = redisHash;
+        this.paperAnswerMapper = paperAnswerMapper;
+        this.examUserMapper = examUserMapper;
+    }
+
+    public PaperDetail getPaperDetail(int paperId, int viewType, int resultId) {
+        long loginUser = UserContext.getUser();
+        ExamUser examUser = examUserMapper.findExamUser(paperId, loginUser);
+        if (examUser == null) {
+            // 用户没有访问试卷的权限
+            return new PaperDetail(paperId, 1);
+        }
+
+        Paper paper = paperMapper.findById(paperId);
+        int role = examUser.getRole();
+
+        Map<Long, QuestionAnswer> paperAnswers;
+        if (viewType == PaperViewType.PaperPreview.getCode()) {
+            if (role > 1) {
+                // 用户只有做题的权限
+                return new PaperDetail(paperId, 3);
+            }
+
+            paperAnswers = getPaperAnswersFromCorrect(paperId);
+        } else if (viewType == PaperViewType.PaperExam.getCode()) {
+            if (role == 1) {
+                // 用户只有管理的权限
+                return new PaperDetail(paperId, 2);
+            }
+
+            paperAnswers = getPaperAnswersFromCache(paperId, loginUser);
+        } else if (viewType == PaperViewType.PaperMark.getCode()) {
+            int markType = paper.getMarkType();
+            if (markType == 2) {
+                // 自己批改
+                if (role == 1) {
+                    // 试卷由用户自己批改
+                    return new PaperDetail(paperId, 4);
+                }
+            } else if (markType == 3) {
+                // 他人批改
+                if (role > 1) {
+                    // 用户只有做题的权限
+                    return new PaperDetail(paperId, 3);
+                }
+            }
+
+            // 批改试卷不需要客观题
+            paperAnswers = getPaperAnswersFromResult(paperId, resultId, false);
+        } else if (viewType == PaperViewType.PaperResult.getCode()) {
+            if (role == 1) {
+                // 用户只有管理的权限
+                return new PaperDetail(paperId, 2);
+            }
+
+            paperAnswers = getPaperAnswersFromResult(paperId, resultId, true);
+        } else {
+            // 试卷访问类型 viewType 未知
+            return new PaperDetail(paperId, 7);
+        }
+
+        int paperViewStatus = 9;
+        Set<Integer> questionTypes = paperAnswers.values().stream()
+                .collect(Collectors.groupingBy(QuestionAnswer::getQuestionType))
+                .keySet();
+        Map<Integer, List<QuestionInfo>> questionMap = getPaperQuestions(paperId).stream()
+                .filter(questionInfo -> {
+                    int questionType = questionInfo.getQuestionType();
+                    return questionTypes.contains(questionType);
+                })
+                .collect(Collectors.groupingBy(QuestionInfo::getQuestionType));
+        UserResult userResult = new UserResult(paperId, paperAnswers);
+        return new PaperDetail(paper, paperViewStatus, questionMap, userResult);
+    }
+
+    public List<QuestionInfo> getPaperQuestions(int paperId) {
+        List<PaperQuestion> paperQuestions = paperQuestionMapper.findByPaperId(paperId);
+        // questionId -> pos
+        Map<Long, PaperQuestion> questionMap = paperQuestions.stream()
+                .collect(Collectors.toMap(PaperQuestion::getQuestionId, k -> k));
+        List<Question> list1 = questionMapper.findByQuestionIds(new ArrayList<>(questionMap.keySet()));
+        return list1.stream().map(question -> {
+            QuestionInfo questionInfo = getQuestionInfo(question, questionMap);
+            long questionId = question.getQuestionId();
+            boolean child = question.getChild();
+            if (child) {
+                List<QuestionInfo> children = getGroupQuestion(paperId, questionId);
+                questionInfo.setChildren(children);
+            }
+            return questionInfo;
+        }).sorted(Comparator.comparingInt(QuestionInfo::getPos)).collect(Collectors.toList());
+    }
+
+    private List<QuestionInfo> getGroupQuestion(int paperId, long pid) {
+        List<Question> questions = questionMapper.findByPid(pid);
+        Map<Long, PaperQuestion> questionMap = paperQuestionMapper.findByPaperIdAndPid(paperId, pid).stream()
+                .collect(Collectors.toMap(PaperQuestion::getQuestionId, k -> k));
+
+        return questions.stream()
+                .map(question -> getQuestionInfo(question, questionMap))
+                .collect(Collectors.toList());
+    }
+
+    private QuestionInfo getQuestionInfo(Question question, Map<Long, PaperQuestion> questionMap) {
+        long questionId = question.getQuestionId();
+        int pos = questionMap.get(questionId).getPos();
+        int score = questionMap.get(questionId).getScore();
+
+        QuestionInfo questionInfo = new QuestionInfo(pos, question, score);
+        List<QuestionOption> list2 = questionOptionMapper.findByQuestionId(questionId);
+        if (!list2.isEmpty()) {
+            List<QuestionAnswerInfo> answerInfos = list2.stream()
+                    .map(QuestionAnswerInfo::new)
+                    .sorted(Comparator.comparingInt(QuestionAnswerInfo::getPos))
+                    .collect(Collectors.toList());
+            questionInfo.setAnswer(answerInfos);
+            questionInfo.setQuestionOptions(answerInfos);
+        }
+
+        return questionInfo;
+    }
+
+    private Map<Long, QuestionAnswer> getPaperAnswersFromCache(int paperId, long userId) {
+        Map<Long, QuestionAnswer> map = new HashMap<>();
+        String key = String.format("%s:%s:%s", keyPrefix, paperId, userId);
+        redisHash.hgetall(key).forEach((k, v) -> {
+            QuestionAnswer questionAnswer = (QuestionAnswer) v;
+            long questionId = questionAnswer.getQuestionId();
+            Object[] submitAnswer = questionAnswer.getSubmitAnswer();
+            int questionType = questionAnswer.getQuestionType();
+            if (questionType == QuestionType.QUESTION1.getCode()) {
+                if (submitAnswer[0] instanceof String) {
+                    Object[] submitAnswer1 = new Object[] {Integer.parseInt((String) submitAnswer[0])};
+                    questionAnswer.setSubmitAnswer(submitAnswer1);
+                }
+            }
+            map.put(questionId, questionAnswer);
+        });
+        if (!map.isEmpty()) {
+            return map;
+        }
+
+        return getEmptyPaperAnswers(paperId);
+    }
+
+    /**
+     * 获取空白的试卷答案
+     *
+     * @param
+     * @return
+     * @date 2025-08-10 18:08:71
+     */
+    private Map<Long, QuestionAnswer> getEmptyPaperAnswers(int paperId) {
+        Map<Long, QuestionAnswer> map = new HashMap<>();
+        List<QuestionInfo> questions = getPaperQuestions(paperId);
+        for (QuestionInfo questionInfo : questions) {
+            QuestionAnswer questionAnswer = new QuestionAnswer(questionInfo);
+            long questionId = questionInfo.getQuestionId();
+            int type = questionInfo.getQuestionType();
+            List<QuestionOption> questionOptionList = questionOptionMapper.findByQuestionId(questionId);
+            long count = questionOptionList.stream().filter(QuestionOption::getCorrect).count();
+            List<Object> list = new ArrayList<>();
+            for (int i = 0; i < count; i++) {
+                if (type == QuestionType.QUESTION1.getCode() || type == QuestionType.QUESTION3.getCode()) {
+                    // 单选题和判断题前端使用 radio, 需要 int 类型来表示选中的 radio
+                    list.add(-1);
+                } else if (type == QuestionType.QUESTION2.getCode()) {
+                    // 多选题前端使用 checkbox, 使用 string[] 来表示选中的 checkbox
+                    // 空数组表示没有选中
+                } else {
+                    // 填空题和简答题使用空字符串占位
+                    list.add("");
+                }
+                questionAnswer.setSubmitAnswer(list.toArray(new Object[0]));
+            }
+
+            map.putIfAbsent(questionId, questionAnswer);
+        }
+
+        return map;
+    }
+
+    private Map<Long, QuestionAnswer> getPaperAnswersFromResult(int paperId, int resultId, boolean hasObjective) {
+        Map<Long, QuestionAnswer> map = new HashMap<>();
+        List<PaperAnswer> paperAnswerList = paperAnswerMapper.findByResultId(resultId);
+        paperAnswerList.forEach(paperAnswer -> {
+            long questionId = paperAnswer.getQuestionId();
+            int questionType = paperAnswer.getQuestionType();
+            if (questionType <= QuestionType.QUESTION3.getCode() && !hasObjective) {
+                return;
+            }
+
+            PaperQuestion paperQuestion = paperQuestionMapper.findPaperQuestion(paperId, questionId);
+            int correctScore = paperQuestion.getScore();
+            QuestionAnswer questionAnswer = new QuestionAnswer(paperAnswer, correctScore);
+
+            setSubmitAnswer(paperAnswer, questionAnswer);
+            setCorrectAnswer(questionAnswer);
+            map.put(questionId, questionAnswer);
+        });
+
+        return map;
+    }
+
+    private void setCorrectAnswer(QuestionAnswer questionAnswer) {
+        long questionId = questionAnswer.getQuestionId();
+        List<QuestionOption> list = questionOptionMapper.findByQuestionId(questionId);
+        List<Object> correctAnswers = list.stream()
+                .filter(QuestionOption::getCorrect)
+                .map(QuestionOption::getContent)
+                .collect(Collectors.toList());
+        questionAnswer.setCorrectAnswer(correctAnswers.toArray(new Object[0]));
+    }
+
+    private void setSubmitAnswer(PaperAnswer paperAnswer, QuestionAnswer questionAnswer) {
+        int type = paperAnswer.getQuestionType();
+        String submitAnswer = paperAnswer.getAnswer();
+        List<Object> submitAnswerList;
+        if (type == QuestionType.QUESTION1.getCode() || type == QuestionType.QUESTION3.getCode()) {
+            // 单选题和判断题前端使用 radio, 需要 int 类型来表示选中的 radio
+            submitAnswerList = List.of(Integer.parseInt(submitAnswer));
+        } else if (type == QuestionType.QUESTION2.getCode()) {
+            // 多选题前端使用 checkbox, 使用 string[] 来表示选中的 checkbox
+            String[] answerArr = submitAnswer.split(",");
+            submitAnswerList = List.of(answerArr);
+        } else if (type == QuestionType.QUESTION4.getCode()) {
+            // 填空题
+            String[] answerArr = submitAnswer.split(",");
+            submitAnswerList = List.of(answerArr);
+        } else if (type == QuestionType.QUESTION5.getCode()) {
+            // 问答题
+            submitAnswerList = List.of(submitAnswer);
+        } else if (type == QuestionType.QUESTION6.getCode()) {
+            // 组合题
+            submitAnswerList = List.of(submitAnswer);
+        } else {
+            submitAnswerList = List.of(submitAnswer);
+        }
+        questionAnswer.setSubmitAnswer(submitAnswerList.toArray(new Object[0]));
+    }
+
+    private Map<Long, QuestionAnswer> getPaperAnswersFromCorrect(int paperId) {
+        Map<Long, QuestionAnswer> map = new HashMap<>();
+        List<PaperQuestion> paperQuestions = paperQuestionMapper.findByPaperId(paperId);
+        Map<Long, PaperQuestion> paperQuestionMap = paperQuestions.stream()
+                .collect(Collectors.toMap(PaperQuestion::getQuestionId, k -> k));
+        //List<Long> questionIds = paperQuestions.stream().map(PaperQuestion::getQuestionId).collect(Collectors.toList());
+        List<Long> questionIds = new ArrayList<>(paperQuestionMap.keySet());
+
+        List<Question> questions = questionMapper.findByQuestionIds(questionIds);
+        questions.forEach(question -> {
+            long questionId = question.getQuestionId();
+            int type = question.getType();
+            int correctScore = paperQuestionMap.get(questionId).getScore();
+            QuestionAnswer questionAnswer = new QuestionAnswer(question, correctScore);
+
+            List<QuestionOption> list = questionOptionMapper.findByQuestionId(questionId);
+            // 正确答案
+            List<Object> correctAnswerList;
+            if (type == QuestionType.QUESTION1.getCode() || type == QuestionType.QUESTION3.getCode()) {
+                correctAnswerList = list.stream()
+                        .filter(QuestionOption::getCorrect)
+                        .map(QuestionOption::getPos)
+                        .collect(Collectors.toList());
+            } else if (type == QuestionType.QUESTION2.getCode()) {
+                correctAnswerList = list.stream()
+                        .filter(QuestionOption::getCorrect)
+                        .map(QuestionOption::getPos)
+                        .collect(Collectors.toList());
+            } else {
+                correctAnswerList = list.stream()
+                        .filter(QuestionOption::getCorrect)
+                        .map(QuestionOption::getContent)
+                        .collect(Collectors.toList());
+            }
+
+            questionAnswer.setCorrectAnswer(correctAnswerList.toArray(new Object[0]));
+            map.put(questionId, questionAnswer);
+        });
+        return map;
+    }
+}

+ 1 - 1
content/content-service/src/main/java/cn/reghao/tnb/content/app/exam/service/SubjectService.java

@@ -8,7 +8,7 @@ import cn.reghao.tnb.content.app.exam.db.mapper.SubjectMapper;
 import cn.reghao.tnb.content.app.exam.model.vo.SubjectInfo;
 import org.springframework.stereotype.Service;
 
-import java.util.List;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**