diff --git a/.idea/compiler.xml b/.idea/compiler.xml index c8b1f76..383a244 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -10,4 +10,9 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..dfc8d74 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 03d9549..49798c3 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,6 +1,69 @@ \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 6c0b863..94a25f7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index 5f10b26..a899b07 100644 --- a/README.md +++ b/README.md @@ -1,108 +1,18 @@ -# spring-boot-online-exam - -> 在线Demo预览,http://129.211.88.191 ,账户分别是admin、teacher、student,密码是admin123。视频讲解代码:https://www.bilibili.com/video/BV1FP4y1L7xt/ - -> 好消息!!!一个小伙伴做了Python实现,欢迎大家star:https://github.com/xingxingzaixian/django-drf-online-exam - -## 1.快速体验 -### 1.1 事先准备 -> clone代码并进入代码路径 - -```shell -git clone git@github.com:lsgwr/spring-boot-online-exam.git -cd spring-boot-online-exam -``` - -下面按照Linux和windows说明快速搭建的方法 -### 1.2 Linux -执行代码下的脚本start.sh即可 - -然后访问 http://ip:80 即可访问自己搭建的考试系统 - -### 1.3 windows -+ 1.安装JDK,推荐JDK8 -+ 2.从官方仓库下载发布的jar包,建议选择最新版:https://github.com/lsgwr/spring-boot-online-exam/releases -+ 3.安装MySQL,创建数据库exam,并初始化密码为aA111111,导入doc/sql/exam.sql文件来创建数据库 -+ 4.启动jar包:`java -jar exam.jar` -+ 5.访问:http://ip:9527 即可访问自己搭建的考试系统 - -## 2.介绍 -基于springboot的在线考试系统 - -### 2.1 功能简介 - -+ 支持单选题、多选题、判断题 -+ 支持学生(student)、教师(teacher)、管理员(admin)三种角色 - + 学生:参加考试和查看我的考试 - + 教师:学生的所有权限+创建/编辑题目+创建/编辑考试 - + 管理员:教师的所有权限+管理用户 - -### 2.3 软件架构 - -> 前后端分离,前段组件化,方便二次开发;后端 - -+ 后端采用SpringBoot+JPA++Swagger2+JWT校验,根据不同用户的权限返回给用户不同的数据 -+ 后端采用Vue+AntDesign,组件化拆分,封装了很多年公共组件,方便维护和二次开发 - -### 2.3 使用教程 - -+ 1.下载代码 - ```shell - git clone https://github.com/19920625lsg/spring-boot-online-exam.git - ``` -+ 2.初始化数据库 - > 安装mysql的步骤这里省略,网上的教程很多。安装好mysql后,新建exam数据库,密码和`spring-boot-online-exam/backend/exam/src/main/resources/application.yml`的`password: xxxxxx`保持一致,然后导入`spring-boot-online-exam/doc/sql/exam.sql` -+ 3.启动后端 - > 打开`spring-boot-online-exam/backend/exam`这个Maven项目,可以在IDE里启动或者执行`mvn install`生成jar包启动 -+ 4.启动前端 - + 进入到前端代码路径 `cd spring-boot-online-exam/frontend/exam/` - + 安装依赖 `npm install` - + 启动前端 `npm run serve` -+ 5.部署完毕,查看效果 - > 打开 http://localhost:8000 或者 http://本机ip:8000 即可查看演示效果 - -## 3.功能图示 - -+ 1.管理题目 - + 1.1 题目列表 - > ![题目查看](doc/images/question_list.png) - + 1.2 题目创建 - > ![题目创建](doc/images/question_create.png) - + 1.3 题目更新 - > ![题目更新](doc/images/question_update.png) -+ 2.考试管理 - + 2.1 考试列表 - > ![考试查看](doc/images/exam_list.png) - + 2.2 考试创建 - > ![考试创建](doc/images/exam_create.png) - + 2.3 考试更新(`还有点小bug,开发中`) - > ![考试更新](doc/images/exam_update.png) -+ 3.我的考试 - + 3.1 参加考试 - > 在"考试列表"模块点击自己想参加的考试卡片即可 - > ![参加考试1](doc/images/exam_join.png) - > ![参加考试2](doc/images/exam_join2.png) - + 3.2 考试记录查看 - > ![考试记录查看](doc/images/exam_detail.png) - -## 4.参与贡献 - -1. Fork 本仓库 -2. 新建 exam_xxx 分支 -3. 提交代码 -4. 新建 Pull Request - -## 5.Todo -+ `√`0.修复issue提地bug:题目创建失败 -+ `√`1.考试详情编辑 -+ 2.支持题目和考试的删除`删除的话比较麻烦,先不做了,最好是弄个visible字段,不实际删除,要不后面有些关联数据找不到就不好了` - > 如果题目有关联的考试则必须先删除对应的考试,反过来删除考试则不用删除题目 -+ 3.图片改成base64存到数据库中 -+ 4.题干和选项支持富文本 -+ 5.支持批量导入题目 -+ 6.新增用户管理、学科管理功能 -+ 7.老师能考到所有学生的成绩以及考试的统计信息 -+ 8.更多的数据分析功能 -+ 9.支持容器化一键式部署(编好Dockerfile) -+ 10.支持移动端,最好用uniapp做 -+ ......抓紧做吧,争取每周末做一点...... + ResultVO getExamRecordDetail(@PathVariable String recordId) { + // 定义返回结果 + ResultVO resultVO; + try { + // 调用examService获取考试记录详情 + RecordDetailVo recordDetailVo = examService.getRecordDetail(recordId); + // 返回成功结果 + resultVO = new ResultVO<>(0, "获取考试记录详情成功", recordDetailVo); + } catch (Exception e) { + // 打印异常信息 + e.printStackTrace(); + // 返回失败结果 + resultVO = new ResultVO<>(-1, "获取考试记录详情失败", null); + } + // 返回结果 + return resultVO; + } +} \ No newline at end of file diff --git a/backend/src/main/java/lsgwr/exam/controller/ExamController.java b/backend/src/main/java/lsgwr/exam/controller/ExamController.java index e6ed4d0..99b50ad 100644 --- a/backend/src/main/java/lsgwr/exam/controller/ExamController.java +++ b/backend/src/main/java/lsgwr/exam/controller/ExamController.java @@ -29,28 +29,39 @@ public class ExamController { @GetMapping("/question/all") @ApiOperation("获取所有问题的列表") - ResultVO> getQuestionAll() { + // 获取全部问题列表。 +ResultVO> getQuestionAll() { + // 定义返回值。 ResultVO> resultVO; try { + // 调用examService获取全部问题列表。 List questionAll = examService.getQuestionAll(); + // 返回成功结果。 resultVO = new ResultVO<>(0, "获取全部问题列表成功", questionAll); } catch (Exception e) { + // 打印异常信息。 e.printStackTrace(); + // 返回失败结果。 resultVO = new ResultVO<>(-1, "获取全部问题列表失败", null); } + // 返回结果。 return resultVO; } @PostMapping("/question/update") @ApiOperation("更新问题") ResultVO questionUpdate(@RequestBody QuestionVo questionVo) { - // 完成问题的更新 + // 完成问题的更新。 System.out.println(questionVo); try { + // 调用examService的updateQuestion方法,更新问题。 QuestionVo questionVoResult = examService.updateQuestion(questionVo); + // 返回更新成功的结果。 return new ResultVO<>(0, "更新问题成功", questionVoResult); } catch (Exception e) { + // 打印异常信息。 e.printStackTrace(); + // 返回更新失败的结果。 return new ResultVO<>(-1, "更新问题失败", null); } } @@ -58,106 +69,134 @@ public class ExamController { @PostMapping("/question/create") @ApiOperation("创建问题") ResultVO questionCreate(@RequestBody QuestionCreateSimplifyVo questionCreateSimplifyVo, HttpServletRequest request) { + // 创建一个QuestionCreateVo对象。 QuestionCreateVo questionCreateVo = new QuestionCreateVo(); - // 把能拷贝过来的属性都拷贝过来 + // 把能拷贝过来的属性都拷贝过来。 BeanUtils.copyProperties(questionCreateSimplifyVo, questionCreateVo); - // 设置创建者信息 + // 设置创建者信息。 String userId = (String) request.getAttribute("user_id"); questionCreateVo.setQuestionCreatorId(userId); System.out.println(questionCreateVo); try { + // 调用examService的questionCreate方法创建问题。 examService.questionCreate(questionCreateVo); + // 返回问题创建成功的ResultVO。 return new ResultVO<>(0, "问题创建成功", null); } catch (Exception e) { + // 打印异常信息。 e.printStackTrace(); + // 返回创建问题失败的ResultVO。 return new ResultVO<>(-1, "创建问题失败", null); } } @GetMapping("/question/selection") @ApiOperation("获取问题分类的相关选项") - ResultVO getSelections() { + // 获取问题分类选项。 +ResultVO getSelections() { + // 调用examService的getSelections方法获取问题分类选项。 QuestionSelectionVo questionSelectionVo = examService.getSelections(); + // 如果获取成功。 if (questionSelectionVo != null) { + // 返回成功的结果。 return new ResultVO<>(0, "获取问题分类选项成功", questionSelectionVo); } else { + // 否则返回失败的结果。 return new ResultVO<>(-1, "获取问题分类选项失败", null); } } - @GetMapping("/question/detail/{id}") @ApiOperation("根据问题的id获取问题的详细信息") ResultVO getQuestionDetail(@PathVariable String id) { - // 根据问题id获取问题的详细信息 + // 根据问题id获取问题的详细信息。 System.out.println(id); ResultVO resultVO; try { + // 调用examService的getQuestionDetail方法,根据问题id获取问题的详细信息。 QuestionDetailVo questionDetailVo = examService.getQuestionDetail(id); + // 如果获取成功,则返回ResultVO对象,状态码为0,提示信息为"获取问题详情成功",数据为questionDetailVo。 resultVO = new ResultVO<>(0, "获取问题详情成功", questionDetailVo); } catch (Exception e) { + // 如果获取失败,则打印异常信息,并返回ResultVO对象,状态码为-1,提示信息为"获取问题详情失败",数据为null。 e.printStackTrace(); resultVO = new ResultVO<>(-1, "获取问题详情失败", null); } + // 返回ResultVO对象。 return resultVO; } - @GetMapping("/all") @ApiOperation("获取全部考试的列表") ResultVO> getExamAll() { - // 需要拼接前端需要的考试列表对象 + // 需要拼接前端需要的考试列表对象。 ResultVO> resultVO; try { + // 调用examService的getExamAll方法获取全部考试的列表。 List examVos = examService.getExamAll(); + // 将获取到的考试列表封装到ResultVO对象中,并返回。 resultVO = new ResultVO<>(0, "获取全部考试的列表成功", examVos); } catch (Exception e) { + // 捕获异常,并打印异常信息。 e.printStackTrace(); + // 将异常信息封装到ResultVO对象中,并返回。 resultVO = new ResultVO<>(-1, "获取全部考试的列表失败", null); } return resultVO; } - @GetMapping("/question/type/list") @ApiOperation("获取问题列表,按照单选、多选和判断题分类返回") ResultVO getExamQuestionTypeList() { - // 获取问题的分类列表 + // 获取问题的分类列表。 ResultVO resultVO; try { + // 调用examService的getExamQuestionType方法获取问题分类列表。 ExamQuestionTypeVo examQuestionTypeVo = examService.getExamQuestionType(); + // 如果获取成功,则返回成功的结果。 resultVO = new ResultVO<>(0, "获取问题列表成功", examQuestionTypeVo); } catch (Exception e) { + // 如果获取失败,则打印异常信息,并返回失败的结果。 e.printStackTrace(); resultVO = new ResultVO<>(-1, "获取问题列表失败", null); } + // 返回结果。 return resultVO; } - @PostMapping("/create") @ApiOperation("创建考试") ResultVO createExam(@RequestBody ExamCreateVo examCreateVo, HttpServletRequest request) { - // 从前端传参数过来,在这里完成考试的入库 + // 从前端传参数过来,在这里完成考试的入库。 ResultVO resultVO; + // 获取当前用户的id。 String userId = (String) request.getAttribute("user_id"); try { + // 调用examService的create方法,将examCreateVo和userId作为参数传入,创建考试。 Exam exam = examService.create(examCreateVo, userId); + // 创建一个ResultVO对象,将创建成功的考试信息返回。 resultVO = new ResultVO<>(0, "创建考试成功", exam); } catch (Exception e) { + // 捕获异常,打印异常信息,并创建一个ResultVO对象,将创建失败的考试信息返回。 e.printStackTrace(); resultVO = new ResultVO<>(-1, "创建考试失败", null); } + // 返回ResultVO对象。 return resultVO; } @PostMapping("/update") @ApiOperation("更新考试") ResultVO updateExam(@RequestBody ExamVo examVo, HttpServletRequest request) { - // 从前端传参数过来,在这里完成考试的入库 + // 从前端传参数过来,在这里完成考试的入库。 ResultVO resultVO; + // 获取当前用户id。 String userId = (String) request.getAttribute("user_id"); try { + // 调用service层更新考试。 Exam exam = examService.update(examVo, userId); + // 返回更新成功的resultVO。 resultVO = new ResultVO<>(0, "更新考试成功", exam); } catch (Exception e) { + // 打印异常信息。 e.printStackTrace(); + // 返回更新失败的resultVO。 resultVO = new ResultVO<>(-1, "更新考试失败", null); } return resultVO; @@ -166,22 +205,26 @@ public class ExamController { @GetMapping("/card/list") @ApiOperation("获取考试列表,适配前端卡片列表") ResultVO> getExamCardList() { - // 获取考试列表卡片 + // 获取考试列表卡片。 ResultVO> resultVO; try { + // 调用examService的getExamCardList方法获取考试列表卡片。 List examCardVoList = examService.getExamCardList(); + // 如果获取成功,则返回成功的结果。 resultVO = new ResultVO<>(0, "获取考试列表卡片成功", examCardVoList); } catch (Exception e) { + // 如果获取失败,则打印异常信息,并返回失败的结果。 e.printStackTrace(); resultVO = new ResultVO<>(-1, "获取考试列表卡片失败", null); } + // 返回结果 return resultVO; } @GetMapping("/detail/{id}") @ApiOperation("根据考试的id,获取考试详情") ResultVO getExamDetail(@PathVariable String id) { - // 根据id获取考试详情 + // 根据id获取考试详情。 ResultVO resultVO; try { ExamDetailVo examDetail = examService.getExamDetail(id); @@ -194,16 +237,20 @@ public class ExamController { @PostMapping("/finish/{examId}") @ApiOperation("根据用户提交的答案对指定id的考试判分") + // 完成考试 ResultVO finishExam(@PathVariable String examId, @RequestBody HashMap> answersMap, HttpServletRequest request) { + // 定义返回结果。 ResultVO resultVO; try { - // 拦截器里设置上的用户id + // 拦截器里设置上的用户id。 String userId = (String) request.getAttribute("user_id"); - // 下面根据用户提交的信息进行判分,返回用户的得分情况 + // 下面根据用户提交的信息进行判分,返回用户的得分情况。 ExamRecord examRecord = examService.judge(userId, examId, answersMap); + // 返回结果。 resultVO = new ResultVO<>(0, "考卷提交成功", examRecord); } catch (Exception e) { e.printStackTrace(); + // 返回错误结果。 resultVO = new ResultVO<>(-1, "考卷提交失败", null); } return resultVO; @@ -211,12 +258,13 @@ public class ExamController { @GetMapping("/record/list") @ApiOperation("获取当前用户的考试记录") + // 获取考试记录列表。 ResultVO> getExamRecordList(HttpServletRequest request) { ResultVO> resultVO; try { - // 拦截器里设置上的用户id + // 拦截器里设置上的用户id。 String userId = (String) request.getAttribute("user_id"); - // 下面根据用户账号拿到他(她所有的考试信息),注意要用VO封装下 + // 下面根据用户账号拿到他(她所有的考试信息),注意要用VO封装下。 List examRecordVoList = examService.getExamRecordList(userId); resultVO = new ResultVO<>(0, "获取考试记录成功", examRecordVoList); } catch (Exception e) { @@ -229,14 +277,20 @@ public class ExamController { @GetMapping("/record/detail/{recordId}") @ApiOperation("根据考试记录id获取考试记录详情") ResultVO getExamRecordDetail(@PathVariable String recordId) { + // 定义返回结果。 ResultVO resultVO; try { + // 调用examService获取考试记录详情。 RecordDetailVo recordDetailVo = examService.getRecordDetail(recordId); + // 返回成功结果。 resultVO = new ResultVO<>(0, "获取考试记录详情成功", recordDetailVo); } catch (Exception e) { + // 打印异常信息。 e.printStackTrace(); + // 返回失败结果。 resultVO = new ResultVO<>(-1, "获取考试记录详情失败", null); } + // 返回结果。 return resultVO; } } diff --git a/backend/src/main/java/lsgwr/exam/controller/UploadDownloadController.java b/backend/src/main/java/lsgwr/exam/controller/UploadDownloadController.java index e9d2392..35c7cc2 100644 --- a/backend/src/main/java/lsgwr/exam/controller/UploadDownloadController.java +++ b/backend/src/main/java/lsgwr/exam/controller/UploadDownloadController.java @@ -42,37 +42,49 @@ public class UploadDownloadController { @ApiOperation("单文件上传,支持同时传入参数") @PostMapping("/api/upload/singleAndparas") - public String uploadFileSingle(@RequestParam("dir") String dir, @RequestParam("file") MultipartFile uploadfile) { + // 单文件上传。 + public String uploadFileSingle(@RequestParam("dir") String dir, @RequestParam("file") MultipartFile uploadfile) { + // 调用FileTransUtil工具类中的uploadFile方法,将上传的文件和目录作为参数传入。 return FileTransUtil.uploadFile(uploadfile, dir); } + // 单文件上传,支持同时传入参数,Model。 @ApiOperation("单文件上传,支持同时传入参数,Model") @PostMapping("/upload/single/model") public String singleUploadFileModel(@ModelAttribute("model") UploadModel2 model) { + // 调用FileTransUtil工具类中的uploadFile方法,将上传的文件和目录作为参数传入。 return FileTransUtil.uploadFile(model.getFile(), model.getDir()); } + // 多文件上传,支持同时传入参数。 @ApiOperation("多文件上传,支持同时传入参数") @PostMapping("upload/multiAndparas") public String uploadFileMulti(@RequestParam("dir") String dir, @RequestParam("files") MultipartFile[] uploadfiles) { + // 调用FileTransUtil工具类中的uploadFiles方法,将上传的文件数组和目录作为参数传入。 return FileTransUtil.uploadFiles(uploadfiles, dir); } + // 多文件上传,支持同时传入参数。 @ApiOperation("多文件上传,支持同时传入参数") @PostMapping(value = "/upload/multi/model") public String multiUploadFileModel(@ModelAttribute(("model")) UploadModel model) { + // 调用FileTransUtil工具类中的uploadFiles方法,将上传的文件数组和目录作为参数传入。 return FileTransUtil.uploadFiles(model.getFiles(), model.getDir()); } + // Get下载文件 @ApiOperation("Get下载文件") @GetMapping(value = "/download/get") public ResponseEntity downloadFileGet(@RequestParam String filePath) throws IOException { + // 调用FileTransUtil工具类中的downloadFile方法,将文件路径作为参数传入。 return FileTransUtil.downloadFile(filePath); } + // Post下载文件 @ApiOperation("Post下载文件") @PostMapping(value = "/download/post") public ResponseEntity downloadFilePost(@RequestBody DownloadQo downloadQo) throws IOException { + // 调用FileTransUtil工具类中的downloadFile方法,将文件路径作为参数传入。 return FileTransUtil.downloadFile(downloadQo.getPath()); } } diff --git a/backend/src/main/java/lsgwr/exam/controller/UserController.java b/backend/src/main/java/lsgwr/exam/controller/UserController.java index 768b84d..af0536a 100644 --- a/backend/src/main/java/lsgwr/exam/controller/UserController.java +++ b/backend/src/main/java/lsgwr/exam/controller/UserController.java @@ -62,18 +62,27 @@ public class UserController { @GetMapping("/user-info") @ApiOperation("获取用户信息") + // 根据请求获取用户信息 ResultVO getUserInfo(HttpServletRequest request) { + // 从请求中获取用户ID String userId = (String) request.getAttribute("user_id"); + // 根据用户ID获取用户信息 UserVo userVo = userService.getUserInfo(userId); + // 返回用户信息 return new ResultVO<>(ResultEnum.GET_INFO_SUCCESS.getCode(), ResultEnum.GET_INFO_SUCCESS.getMessage(), userVo); } @GetMapping("/info") @ApiOperation("获取用户的详细信息,包括个人信息页面和操作权限") + // 获取用户信息的接口 ResultVO getInfo(HttpServletRequest request) { + // 打印进入接口的日志 System.out.println("进入/user/info的获取用户信息的接口"); + // 获取用户ID String userId = (String) request.getAttribute("user_id"); + // 调用userService获取用户信息 UserInfoVo userInfoVo = userService.getInfo(userId); + // 返回结果 return new ResultVO<>(ResultEnum.GET_INFO_SUCCESS.getCode(), ResultEnum.GET_INFO_SUCCESS.getMessage(), userInfoVo); } @@ -87,4 +96,4 @@ public class UserController { System.out.println("用户名:" + username); return "用户id:" + userId + "\n用户名:" + username; } -} +} \ No newline at end of file diff --git a/backend/src/main/java/lsgwr/exam/service/impl/ExamServiceImpl.java b/backend/src/main/java/lsgwr/exam/service/impl/ExamServiceImpl.java index e58d825..ca1099d 100644 --- a/backend/src/main/java/lsgwr/exam/service/impl/ExamServiceImpl.java +++ b/backend/src/main/java/lsgwr/exam/service/impl/ExamServiceImpl.java @@ -10,8 +10,8 @@ import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import lsgwr.exam.entity.*; import lsgwr.exam.enums.QuestionEnum; -import lsgwr.exam.service.ExamService; import lsgwr.exam.repository.*; +import lsgwr.exam.service.ExamService; import lsgwr.exam.vo.*; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; @@ -40,9 +40,9 @@ public class ExamServiceImpl implements ExamService { private final QuestionOptionRepository questionOptionRepository; public ExamServiceImpl(QuestionRepository questionRepository, UserRepository userRepository, QuestionLevelRepository questionLevelRepository, QuestionTypeRepository questionTypeRepository, QuestionCategoryRepository questionCategoryRepository, QuestionOptionRepository questionOptionRepository, ExamRepository examRepository, ExamRecordRepository examRecordRepository) { - this.questionRepository = questionRepository; - this.userRepository = userRepository; - this.questionLevelRepository = questionLevelRepository; + this.questionRepository = questionRepository;// + this.userRepository = userRepository;// + this.questionLevelRepository = questionLevelRepository;// this.questionTypeRepository = questionTypeRepository; this.questionCategoryRepository = questionCategoryRepository; this.questionOptionRepository = questionOptionRepository; @@ -57,9 +57,9 @@ public class ExamServiceImpl implements ExamService { } private List getQuestionVos(List questionList) { - // 需要自定义的question列表 + // 需要自定义的question列表, List questionVoList = new ArrayList<>(); - // 循环完成每个属性的定制 + // 循环完成每个属性的定制, for (Question question : questionList) { QuestionVo questionVo = getQuestionVo(question); questionVoList.add(questionVo); @@ -69,9 +69,9 @@ public class ExamServiceImpl implements ExamService { private QuestionVo getQuestionVo(Question question) { QuestionVo questionVo = new QuestionVo(); - // 先复制能复制的属性 + // 先复制能复制的属性, BeanUtils.copyProperties(question, questionVo); - // 设置问题的创建者 + // 设置问题的创建者。 questionVo.setQuestionCreator( Objects.requireNonNull( userRepository.findById( @@ -79,7 +79,7 @@ public class ExamServiceImpl implements ExamService { ).orElse(null) ).getUserUsername()); - // 设置问题的难度 + // 设置问题的难度。 questionVo.setQuestionLevel( Objects.requireNonNull( questionLevelRepository.findById( @@ -87,7 +87,7 @@ public class ExamServiceImpl implements ExamService { ).orElse(null) ).getQuestionLevelDescription()); - // 设置题目的类别,比如单选、多选、判断等 + // 设置题目的类别,比如单选、多选、判断等。 questionVo.setQuestionType( Objects.requireNonNull( questionTypeRepository.findById( @@ -95,7 +95,7 @@ public class ExamServiceImpl implements ExamService { ).orElse(null) ).getQuestionTypeDescription()); - // 设置题目分类,比如数学、语文、英语、生活、人文等 + // 设置题目分类,比如数学、语文、英语、生活、人文等。 questionVo.setQuestionCategory( Objects.requireNonNull( questionCategoryRepository.findById( @@ -104,20 +104,20 @@ public class ExamServiceImpl implements ExamService { ).getQuestionCategoryName() ); - // 选项的自定义Vo列表 + // 选项的自定义Vo列表。 List optionVoList = new ArrayList<>(); - // 获得所有的选项列表 + // 获得所有的选项列表。 List optionList = questionOptionRepository.findAllById( Arrays.asList(question.getQuestionOptionIds().split("-")) ); - // 获取所有的答案列表optionList中每个option的isAnswer选项 + // 获取所有的答案列表optionList中每个option的isAnswer选项。 List answerList = questionOptionRepository.findAllById( Arrays.asList(question.getQuestionAnswerOptionIds().split("-")) ); - // 根据选项和答案的id相同设置optionVo的isAnswer属性 + // 根据选项和答案的id相同设置optionVo的isAnswer属性。 for (QuestionOption option : optionList) { QuestionOptionVo optionVo = new QuestionOptionVo(); BeanUtils.copyProperties(option, optionVo); @@ -129,14 +129,14 @@ public class ExamServiceImpl implements ExamService { optionVoList.add(optionVo); } - // 设置题目的所有选项 + // 设置题目的所有选项。 questionVo.setQuestionOptionVoList(optionVoList); return questionVo; } @Override public QuestionVo updateQuestion(QuestionVo questionVo) { - // 1.把需要的属性都设置好 + // 1.把需要的属性都设置好。 StringBuilder questionAnswerOptionIds = new StringBuilder(); List questionOptionList = new ArrayList<>(); List questionOptionVoList = questionVo.getQuestionOptionVoList(); @@ -148,26 +148,26 @@ public class ExamServiceImpl implements ExamService { questionOptionList.add(questionOption); if (questionOptionVo.getAnswer()) { if (i != size - 1) { - // 把更新后的答案的id加上去,记得用-连到一起 + // 把更新后的答案的id加上去,记得用-连到一起。 questionAnswerOptionIds.append(questionOptionVo.getQuestionOptionId()).append("-"); } else { - // 最后一个不需要用-连接 + // 最后一个不需要用-连接。 questionAnswerOptionIds.append(questionOptionVo.getQuestionOptionId()); } } } - // 1.更新问题 + // 1.更新问题。 Question question = questionRepository.findById(questionVo.getQuestionId()).orElse(null); assert question != null; BeanUtils.copyProperties(questionVo, question); question.setQuestionAnswerOptionIds(questionAnswerOptionIds.toString()); questionRepository.save(question); - // 2.更新所有的option + // 2.更新所有的option。 questionOptionRepository.saveAll(questionOptionList); - // 返回更新后的问题,方便前端局部刷新 + // 返回更新后的问题,方便前端局部刷新。 return getQuestionVo(question); } @@ -175,58 +175,64 @@ public class ExamServiceImpl implements ExamService { public void questionCreate(QuestionCreateVo questionCreateVo) { // 问题创建 Question question = new Question(); - // 把能复制的属性都复制过来 + // 把能复制的属性都复制过来。 BeanUtils.copyProperties(questionCreateVo, question); - // 设置下questionOptionIds和questionAnswerOptionIds,需要自己用Hutool生成下 + // 设置下questionOptionIds和questionAnswerOptionIds,需要自己用Hutool生成下。 List questionOptionList = new ArrayList<>(); List questionOptionCreateVoList = questionCreateVo.getQuestionOptionCreateVoList(); for (QuestionOptionCreateVo questionOptionCreateVo : questionOptionCreateVoList) { QuestionOption questionOption = new QuestionOption(); - // 设置选项的的内容 + // 设置选项的的内容。 questionOption.setQuestionOptionContent(questionOptionCreateVo.getQuestionOptionContent()); - // 设置选项的id + // 设置选项的id。 questionOption.setQuestionOptionId(IdUtil.simpleUUID()); questionOptionList.add(questionOption); } - // 把选项都存起来,然后才能用于下面设置Question的questionOptionIds和questionAnswerOptionIds + // 把选项都存起来,然后才能用于下面设置Question的questionOptionIds和questionAnswerOptionIds。 questionOptionRepository.saveAll(questionOptionList); String questionOptionIds = ""; String questionAnswerOptionIds = ""; - // 经过上面的saveAll方法,所有的option的主键id都已经持久化了 + // 经过上面的saveAll方法,所有的option的主键id都已经持久化了。 for (int i = 0; i < questionOptionCreateVoList.size(); i++) { - // 获取指定选项 + // 获取指定选项。 QuestionOptionCreateVo questionOptionCreateVo = questionOptionCreateVoList.get(i); - // 获取保存后的指定对象 + // 获取保存后的指定对象。 QuestionOption questionOption = questionOptionList.get(i); questionOptionIds += questionOption.getQuestionOptionId() + "-"; if (questionOptionCreateVo.getAnswer()) { - // 如果是答案的话 + // 如果是答案的话。 questionAnswerOptionIds += questionOption.getQuestionOptionId() + "-"; } } - // 把字符串最后面的"-"给去掉 + // 把字符串最后面的"-"给去掉。 questionAnswerOptionIds = replaceLastSeparator(questionAnswerOptionIds); questionOptionIds = replaceLastSeparator(questionOptionIds); - // 设置选项id组成的字符串 + // 设置选项id组成的字符串。 question.setQuestionOptionIds(questionOptionIds); - // 设置答案选项id组成的字符串 + // 设置答案选项id组成的字符串。 question.setQuestionAnswerOptionIds(questionAnswerOptionIds); - // 自己生成问题的id + // 自己生成问题的id。 question.setQuestionId(IdUtil.simpleUUID()); - // 先把创建时间和更新时间每次都取当前时间吧 + // 先把创建时间和更新时间每次都取当前时间吧。 question.setCreateTime(new Date()); question.setUpdateTime(new Date()); - // 保存问题到数据库 + // 保存问题到数据库。 questionRepository.save(question); } @Override + // 重写父类方法,获取问题选择列表 public QuestionSelectionVo getSelections() { + // 创建问题选择对象 QuestionSelectionVo questionSelectionVo = new QuestionSelectionVo(); + // 设置问题类别列表 questionSelectionVo.setQuestionCategoryList(questionCategoryRepository.findAll()); + // 设置问题等级列表 questionSelectionVo.setQuestionLevelList(questionLevelRepository.findAll()); + // 设置问题类型列表 questionSelectionVo.setQuestionTypeList(questionTypeRepository.findAll()); + // 返回问题选择对象 return questionSelectionVo; } @@ -245,10 +251,15 @@ public class ExamServiceImpl implements ExamService { @Override public QuestionDetailVo getQuestionDetail(String id) { + // 根据id查询问题 Question question = questionRepository.findById(id).orElse(null); + // 创建问题详情对象 QuestionDetailVo questionDetailVo = new QuestionDetailVo(); + // 设置问题id questionDetailVo.setId(id); + // 设置问题名称 questionDetailVo.setName(question.getQuestionName()); + // 设置问题描述 questionDetailVo.setDescription(question.getQuestionDescription()); // 问题类型,单选题/多选题/判断题 questionDetailVo.setType( @@ -258,10 +269,10 @@ public class ExamServiceImpl implements ExamService { ).orElse(null) ).getQuestionTypeDescription() ); - // 获取当前问题的选项 + // 获取当前问题的选项。 String optionIdsStr = trimMiddleLine(question.getQuestionOptionIds()); String[] optionIds = optionIdsStr.split("-"); - // 获取选项列表 + // 获取选项列表。 List optionList = questionOptionRepository.findAllById(Arrays.asList(optionIds)); questionDetailVo.setOptions(optionList); return questionDetailVo; @@ -274,14 +285,14 @@ public class ExamServiceImpl implements ExamService { } private List getExamVos(List examList) { - // 需要自定义的exam列表 + // 需要自定义的exam列表。 List examVoList = new ArrayList<>(); - // 循环完成每个属性的定制 + // 循环完成每个属性的定制。 for (Exam exam : examList) { ExamVo examVo = new ExamVo(); - // 先尽量复制能复制的所有属性 + // 先尽量复制能复制的所有属性。 BeanUtils.copyProperties(exam, examVo); - // 设置问题的创建者 + // 设置问题的创建者。 examVo.setExamCreator( Objects.requireNonNull( userRepository.findById( @@ -290,7 +301,7 @@ public class ExamServiceImpl implements ExamService { ).getUserUsername() ); - // 获取所有单选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上 + // 获取所有单选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上。 List radioQuestionVoList = new ArrayList<>(); List radioQuestionList = questionRepository.findAllById( Arrays.asList(exam.getExamQuestionIdsRadio().split("-")) @@ -298,12 +309,12 @@ public class ExamServiceImpl implements ExamService { for (Question question : radioQuestionList) { ExamQuestionSelectVo radioQuestionVo = new ExamQuestionSelectVo(); BeanUtils.copyProperties(question, radioQuestionVo); - radioQuestionVo.setChecked(true); // 考试中的问题肯定被选中的 + radioQuestionVo.setChecked(true); // 考试中的问题肯定被选中的。 radioQuestionVoList.add(radioQuestionVo); } examVo.setExamQuestionSelectVoRadioList(radioQuestionVoList); - // 获取所有多选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上 + // 获取所有多选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上。 List checkQuestionVoList = new ArrayList<>(); List checkQuestionList = questionRepository.findAllById( Arrays.asList(exam.getExamQuestionIdsCheck().split("-")) @@ -311,12 +322,12 @@ public class ExamServiceImpl implements ExamService { for (Question question : checkQuestionList) { ExamQuestionSelectVo checkQuestionVo = new ExamQuestionSelectVo(); BeanUtils.copyProperties(question, checkQuestionVo); - checkQuestionVo.setChecked(true); // 考试中的问题肯定被选中的 + checkQuestionVo.setChecked(true); // 考试中的问题肯定被选中的。 checkQuestionVoList.add(checkQuestionVo); } examVo.setExamQuestionSelectVoCheckList(checkQuestionVoList); - // 获取所有多选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上 + // 获取所有多选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上。 List judgeQuestionVoList = new ArrayList<>(); List judgeQuestionList = questionRepository.findAllById( Arrays.asList(exam.getExamQuestionIdsJudge().split("-")) @@ -324,7 +335,7 @@ public class ExamServiceImpl implements ExamService { for (Question question : judgeQuestionList) { ExamQuestionSelectVo judgeQuestionVo = new ExamQuestionSelectVo(); BeanUtils.copyProperties(question, judgeQuestionVo); - judgeQuestionVo.setChecked(true); // 考试中的问题肯定被选中的 + judgeQuestionVo.setChecked(true); // 考试中的问题肯定被选中的。 judgeQuestionVoList.add(judgeQuestionVo); } examVo.setExamQuestionSelectVoJudgeList(judgeQuestionVoList); @@ -340,7 +351,7 @@ public class ExamServiceImpl implements ExamService { @Override public ExamQuestionTypeVo getExamQuestionType() { ExamQuestionTypeVo examQuestionTypeVo = new ExamQuestionTypeVo(); - // 获取所有单选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上 + // 获取所有单选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上。 List radioQuestionVoList = new ArrayList<>(); List radioQuestionList = questionRepository.findByQuestionTypeId(QuestionEnum.RADIO.getId()); for (Question question : radioQuestionList) { @@ -350,7 +361,7 @@ public class ExamServiceImpl implements ExamService { } examQuestionTypeVo.setExamQuestionSelectVoRadioList(radioQuestionVoList); - // 获取所有多选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上 + // 获取所有多选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上。 List checkQuestionVoList = new ArrayList<>(); List checkQuestionList = questionRepository.findByQuestionTypeId(QuestionEnum.CHECK.getId()); for (Question question : checkQuestionList) { @@ -360,7 +371,7 @@ public class ExamServiceImpl implements ExamService { } examQuestionTypeVo.setExamQuestionSelectVoCheckList(checkQuestionVoList); - // 获取所有多选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上 + // 获取所有多选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上。 List judgeQuestionVoList = new ArrayList<>(); List judgeQuestionList = questionRepository.findByQuestionTypeId(QuestionEnum.JUDGE.getId()); for (Question question : judgeQuestionList) { @@ -374,7 +385,7 @@ public class ExamServiceImpl implements ExamService { @Override public Exam create(ExamCreateVo examCreateVo, String userId) { - // 在线考试系统创建 + // 在线考试系统创建。 Exam exam = new Exam(); BeanUtils.copyProperties(examCreateVo, exam); exam.setExamId(IdUtil.simpleUUID()); @@ -382,35 +393,53 @@ public class ExamServiceImpl implements ExamService { exam.setCreateTime(new Date()); exam.setUpdateTime(new Date()); // Todo:这两个日志后面是要在前端传入的,这里暂时定为当前日期 - exam.setExamStartDate(new Date()); - exam.setExamEndDate(new Date()); - String radioIdsStr = ""; - String checkIdsStr = ""; - String judgeIdsStr = ""; - List radios = examCreateVo.getRadios(); - List checks = examCreateVo.getChecks(); - List judges = examCreateVo.getJudges(); - int radioCnt = 0, checkCnt = 0, judgeCnt = 0; - for (ExamQuestionSelectVo radio : radios) { - if (radio.getChecked()) { - radioIdsStr += radio.getQuestionId() + "-"; - radioCnt++; - } - } - radioIdsStr = replaceLastSeparator(radioIdsStr); - for (ExamQuestionSelectVo check : checks) { - if (check.getChecked()) { - checkIdsStr += check.getQuestionId() + "-"; - checkCnt++; - } - } - checkIdsStr = replaceLastSeparator(checkIdsStr); - for (ExamQuestionSelectVo judge : judges) { - if (judge.getChecked()) { - judgeIdsStr += judge.getQuestionId() + "-"; - judgeCnt++; - } - } + // 设置考试开始时间和结束时间 + exam.setExamStartDate(new Date()); + exam.setExamEndDate(new Date()); + // 初始化选择题、判断题和单选题的id字符串 + String radioIdsStr = ""; + String checkIdsStr = ""; + String judgeIdsStr = ""; + // 获取选择题、判断题和单选题的列表 + List radios = examCreateVo.getRadios(); + List checks = examCreateVo.getChecks(); + List judges = examCreateVo.getJudges(); + // 初始化选择题、判断题和单选题的计数器 + int radioCnt = 0, checkCnt = 0, judgeCnt = 0; + // 遍历单选题列表 + for (ExamQuestionSelectVo radio : radios) { + // 如果单选题被选中 + if (radio.getChecked()) { + // 将单选题的id添加到字符串中 + radioIdsStr += radio.getQuestionId() + "-"; + // 单选题计数器加1 + radioCnt++; + } + } + // 去掉最后一个分隔符 + radioIdsStr = replaceLastSeparator(radioIdsStr); + // 遍历判断题列表 + for (ExamQuestionSelectVo check : checks) { + // 如果判断题被选中 + if (check.getChecked()) { + // 将判断题的id添加到字符串中 + checkIdsStr += check.getQuestionId() + "-"; + // 判断题计数器加1 + checkCnt++; + } + } + // 去掉最后一个分隔符 + checkIdsStr = replaceLastSeparator(checkIdsStr); + // 遍历选择题列表 + for (ExamQuestionSelectVo judge : judges) { + // 如果选择题被选中 + if (judge.getChecked()) { + // 将选择题的id添加到字符串中 + judgeIdsStr += judge.getQuestionId() + "-"; + // 选择题计数器加1 + judgeCnt++; + } + } judgeIdsStr = replaceLastSeparator(judgeIdsStr); exam.setExamQuestionIds(radioIdsStr + "-" + checkIdsStr + "-" + judgeIdsStr); // 设置各个题目的id @@ -427,49 +456,64 @@ public class ExamServiceImpl implements ExamService { @Override public Exam update(ExamVo examVo, String userId) { + // 创建一个新的Exam对象 Exam exam = new Exam(); + // 将examVo中的属性值复制到exam对象中 BeanUtils.copyProperties(examVo, exam); - exam.setExamCreatorId(userId); // 考试的更新人为最新的创建人 - exam.setUpdateTime(new Date()); // 考试的更新日期要记录下 + // 设置考试更新人为最新的创建人 + exam.setExamCreatorId(userId); + // 记录考试更新日期 + exam.setUpdateTime(new Date()); + // 初始化选择题、判断题、单选题的id字符串 String radioIdsStr = ""; String checkIdsStr = ""; - String judgeIdsStr = ""; + String judgeIdsStr = "";//djfndiosbchjdshjiowceduikmn + // 获取选择题、判断题、单选题的列表 List radios = examVo.getExamQuestionSelectVoRadioList(); List checks = examVo.getExamQuestionSelectVoCheckList(); List judges = examVo.getExamQuestionSelectVoJudgeList(); + // 初始化选择题、判断题、单选题的数量 int radioCnt = 0, checkCnt = 0, judgeCnt = 0; + // 遍历选择题列表,将选中的题目id添加到字符串中,并统计选择题数量 for (ExamQuestionSelectVo radio : radios) { if (radio.getChecked()) { radioIdsStr += radio.getQuestionId() + "-"; radioCnt++; } } + // 去除最后一个分隔符 radioIdsStr = replaceLastSeparator(radioIdsStr); + // 遍历判断题列表,将选中的题目id添加到字符串中,并统计判断题数量 for (ExamQuestionSelectVo check : checks) { if (check.getChecked()) { checkIdsStr += check.getQuestionId() + "-"; checkCnt++; } } + // 去除最后一个分隔符 checkIdsStr = replaceLastSeparator(checkIdsStr); + // 遍历单选题列表,将选中的题目id添加到字符串中,并统计单选题数量 for (ExamQuestionSelectVo judge : judges) { if (judge.getChecked()) { judgeIdsStr += judge.getQuestionId() + "-"; judgeCnt++; } } + // 去除最后一个分隔符。 judgeIdsStr = replaceLastSeparator(judgeIdsStr); + // 设置各个题目的id。 exam.setExamQuestionIds(radioIdsStr + "-" + checkIdsStr + "-" + judgeIdsStr); - // 设置各个题目的id exam.setExamQuestionIdsRadio(radioIdsStr); exam.setExamQuestionIdsCheck(checkIdsStr); exam.setExamQuestionIdsJudge(judgeIdsStr); - // 计算总分数 + // 计算总分数。 int examScore = radioCnt * exam.getExamScoreRadio() + checkCnt * exam.getExamScoreCheck() + judgeCnt * exam.getExamScoreJudge(); exam.setExamScore(examScore); + // 保存exam对象。 examRepository.save(exam); + // 返回exam对象。 return exam; } @@ -499,53 +543,53 @@ public class ExamServiceImpl implements ExamService { @Override public ExamRecord judge(String userId, String examId, HashMap> answersMap) { - // 开始考试判分啦~~~ - // 1.首先获取考试对象和选项数组 + // 开始考试判分啦~~~。 + // 1.首先获取考试对象和选项数组。 ExamDetailVo examDetailVo = getExamDetail(examId); Exam exam = examDetailVo.getExam(); - // 2.然后获取该考试下所有的题目信息 + // 2.然后获取该考试下所有的题目信息。 List questionIds = new ArrayList<>(); - // 2.1 题目id的数组 + // 2.1 题目id的数组。 List radioIdList = Arrays.asList(examDetailVo.getRadioIds()); List checkIdList = Arrays.asList(examDetailVo.getCheckIds()); List judgeIdList = Arrays.asList(examDetailVo.getJudgeIds()); questionIds.addAll(radioIdList); questionIds.addAll(checkIdList); questionIds.addAll(judgeIdList); - // 2.2 每种题目的分数 + // 2.2 每种题目的分数。 int radioScore = exam.getExamScoreRadio(); int checkScore = exam.getExamScoreCheck(); int judgeScore = exam.getExamScoreJudge(); - // 2.3 根据问题id的数组拿到所有的问题对象,供下面步骤用 + // 2.3 根据问题id的数组拿到所有的问题对象,供下面步骤用。 List questionList = questionRepository.findAllById(questionIds); Map questionMap = new HashMap<>(); for (Question question : questionList) { questionMap.put(question.getQuestionId(), question); } - // 3.根据正确答案和用户作答信息进行判分 + // 3.根据正确答案和用户作答信息进行判分。 Set questionIdsAnswer = answersMap.keySet(); - // 存储当前考试每个题目的得分情况 + // 存储当前考试每个题目的得分情况。 Map judgeMap = new HashMap<>(); - // 考生作答地每个题目的选项(题目和题目之间用$分隔,题目有多个选项地话用-分隔,题目和选项之间用_分隔),用于查看考试详情 - // 例子:题目1的id_作答选项1-作答选项2&题目2的id_作答选项1&题目3_作答选项1-作答选项2-作答选项3 + // 考生作答地每个题目的选项(题目和题目之间用$分隔,题目有多个选项地话用-分隔,题目和选项之间用_分隔),用于查看考试详情。 + // 例子:题目1的id_作答选项1-作答选项2&题目2的id_作答选项1&题目3_作答选项1-作答选项2-作答选项3。 StringBuilder answerOptionIdsSb = new StringBuilder(); - // 用户此次考试的总分 + // 用户此次考试的总分。 int totalScore = 0; for (String questionId : questionIdsAnswer) { - // 获取用户作答地这个题的答案信息 + // 获取用户作答地这个题的答案信息。 Question question = questionMap.get(questionId); - // 获取答案选项 + // 获取答案选项。 String questionAnswerOptionIds = replaceLastSeparator(question.getQuestionAnswerOptionIds()); List questionAnswerOptionIdList = Arrays.asList(questionAnswerOptionIds.split("-")); Collections.sort(questionAnswerOptionIdList); String answerStr = listConcat(questionAnswerOptionIdList); - // 获取用户作答 + // 获取用户作答。 List questionUserOptionIdList = answersMap.get(questionId); Collections.sort(questionUserOptionIdList); String userStr = listConcat(questionUserOptionIdList); - // 判断questionAnswerOptionIds和answersMap里面的答案是否相等 + // 判断questionAnswerOptionIds和answersMap里面的答案是否相等。 if (answerStr.equals(userStr)) { - // 说明题目作答正确,下面根据题型给分 + // 说明题目作答正确,下面根据题型给分。 int score = 0; if (radioIdList.contains(questionId)) { score = radioScore; @@ -556,22 +600,22 @@ public class ExamServiceImpl implements ExamService { if (judgeIdList.contains(questionId)) { score = judgeScore; } - // 累计本次考试得分 + // 累计本次考试得分。 totalScore += score; - // True代表题目答对 + // True代表题目答对。 answerOptionIdsSb.append(questionId + "@True_" + userStr + "$"); judgeMap.put(questionId, score); } else { - // 说明题目作答错误,直接判零分,False代表题目答错 + // 说明题目作答错误,直接判零分,False代表题目答错。 answerOptionIdsSb.append(questionId + "@False_" + userStr + "$"); judgeMap.put(questionId, 0); } } - // 4.计算得分,记录本次考试结果,存到ExamRecord中 + // 4.计算得分,记录本次考试结果,存到ExamRecord中。 ExamRecord examRecord = new ExamRecord(); examRecord.setExamRecordId(IdUtil.simpleUUID()); examRecord.setExamId(examId); - // 注意去掉最后可能有的&_- + // 注意去掉最后可能有的&_-。 examRecord.setAnswerOptionIds(replaceLastSeparator(answerOptionIdsSb.toString())); examRecord.setExamJoinerId(userId); examRecord.setExamJoinDate(new Date()); @@ -582,58 +626,60 @@ public class ExamServiceImpl implements ExamService { @Override public List getExamRecordList(String userId) { - // 获取指定用户下的考试记录列表 + // 获取指定用户下的考试记录列表。 List examRecordList = examRecordRepository.findByExamJoinerIdOrderByExamJoinDateDesc(userId); List examRecordVoList = new ArrayList<>(); for (ExamRecord examRecord : examRecordList) { ExamRecordVo examRecordVo = new ExamRecordVo(); + // 根据考试记录中的考试ID获取考试信息 Exam exam = examRepository.findById(examRecord.getExamId()).orElse(null); examRecordVo.setExam(exam); + // 根据用户ID获取用户信息 User user = userRepository.findById(userId).orElse(null); examRecordVo.setUser(user); + // 设置考试记录信息 examRecordVo.setExamRecord(examRecord); examRecordVoList.add(examRecordVo); } return examRecordVoList; } - @Override public RecordDetailVo getRecordDetail(String recordId) { - // 获取考试详情的封装对象 + // 获取考试详情的封装对象。 ExamRecord record = examRecordRepository.findById(recordId).orElse(null); RecordDetailVo recordDetailVo = new RecordDetailVo(); recordDetailVo.setExamRecord(record); - // 用户的答案,需要解析 + // 用户的答案,需要解析。 HashMap> answersMap = new HashMap<>(); HashMap resultsMap = new HashMap<>(); assert record != null; String answersStr = record.getAnswerOptionIds(); - // $分隔题目,因为$在正则中有特殊用途(行尾),所以需要括起来 + // $分隔题目,因为$在正则中有特殊用途(行尾),所以需要括起来。 String[] questionArr = answersStr.split("[$]"); for (String questionStr : questionArr) { System.out.println(questionStr); - // 区分开题目标题和选项 + // 区分开题目标题和选项。 String[] questionTitleResultAndOption = questionStr.split("_"); String[] questionTitleAndResult = questionTitleResultAndOption[0].split("@"); String[] questionOptions = questionTitleResultAndOption[1].split("-"); - // 题目:答案选项 + // 题目:答案选项。 answersMap.put(questionTitleAndResult[0], Arrays.asList(questionOptions)); - // 题目:True / False + // 题目:True / False。 resultsMap.put(questionTitleAndResult[0], questionTitleAndResult[1]); } recordDetailVo.setAnswersMap(answersMap); recordDetailVo.setResultsMap(resultsMap); - // 下面再计算正确答案的map + // 下面再计算正确答案的map。 ExamDetailVo examDetailVo = getExamDetail(record.getExamId()); List questionIdList = new ArrayList<>(); questionIdList.addAll(Arrays.asList(examDetailVo.getRadioIds())); questionIdList.addAll(Arrays.asList(examDetailVo.getCheckIds())); questionIdList.addAll(Arrays.asList(examDetailVo.getJudgeIds())); - // 获取所有的问题对象 + // 获取所有的问题对象。 List questionList = questionRepository.findAllById(questionIdList); HashMap> answersRightMap = new HashMap<>(); for (Question question : questionList) { - // 记得去掉最后可能出现的特殊字符 + // 记得去掉最后可能出现的特殊字符。 String questionAnswerOptionIdsStr = replaceLastSeparator(question.getQuestionAnswerOptionIds()); String[] questionAnswerOptionIds = questionAnswerOptionIdsStr.split("-"); answersRightMap.put(question.getQuestionId(), Arrays.asList(questionAnswerOptionIds)); @@ -641,7 +687,6 @@ public class ExamServiceImpl implements ExamService { recordDetailVo.setAnswersRightMap(answersRightMap); return recordDetailVo; } - /** * 把字符串最后一个字符-替换掉 * @@ -650,7 +695,7 @@ public class ExamServiceImpl implements ExamService { */ private String replaceLastSeparator(String str) { String lastChar = str.substring(str.length() - 1); - // 题目和题目之间用$分隔,题目有多个选项地话用-分隔,题目和选项之间用_分隔 + // 题目和题目之间用$分隔,题目有多个选项地话用-分隔,题目和选项之间用_分隔。 if ("-".equals(lastChar) || "_".equals(lastChar) || "$".equals(lastChar)) { str = StrUtil.sub(str, 0, str.length() - 1); } @@ -672,3 +717,4 @@ public class ExamServiceImpl implements ExamService { return replaceLastSeparator(sb.toString()); } } +//注释完毕 \ No newline at end of file diff --git a/frontend/src/components/Menu/menu.js b/frontend/src/components/Menu/menu.js index 458c19f..be5b0e0 100644 --- a/frontend/src/components/Menu/menu.js +++ b/frontend/src/components/Menu/menu.js @@ -6,20 +6,24 @@ const { Item, SubMenu } = Menu export default { name: 'SMenu', props: { + // 菜单数据 menu: { type: Array, required: true }, + // 主题 theme: { type: String, required: false, default: 'dark' }, + // 模式 mode: { type: String, required: false, default: 'inline' }, + // 是否折叠 collapsed: { type: Boolean, required: false, @@ -28,12 +32,16 @@ export default { }, data () { return { + // 打开的菜单项 openKeys: [], + // 选择的菜单项 selectedKeys: [], + // 缓存的打开的菜单项 cachedOpenKeys: [] } }, computed: { + // 根菜单项的key rootSubmenuKeys: vm => { const keys = [] vm.menu.forEach(item => keys.push(item.path)) @@ -41,23 +49,29 @@ export default { } }, mounted () { + // 组件挂载时更新菜单 this.updateMenu() }, watch: { + // 监听折叠状态的变化 collapsed (val) { if (val) { + // 折叠时,缓存打开的菜单项 this.cachedOpenKeys = this.openKeys.concat() this.openKeys = [] } else { + // 展开时,恢复打开的菜单项 this.openKeys = this.cachedOpenKeys } }, + // 监听路由的变化 $route: function () { + // 更新菜单 this.updateMenu() } }, methods: { - // select menu item + // 选择菜单项 onOpenChange (openKeys) { // 在水平模式下时执行,并且不再执行后续 if (this.mode === 'horizontal') { @@ -72,6 +86,7 @@ export default { this.openKeys = latestOpenKey ? [latestOpenKey] : [] } }, + // 更新菜单 updateMenu () { const routes = this.$route.matched.concat() const { hidden } = this.$route.meta @@ -91,13 +106,14 @@ export default { this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys) }, - // render + // 渲染 renderItem (menu) { if (!menu.hidden) { return menu.children && !menu.hideChildrenInMenu ? this.renderSubMenu(menu) : this.renderMenuItem(menu) } return null }, + // 渲染菜单项 renderMenuItem (menu) { const target = menu.meta.target || null const tag = target && 'a' || 'router-link' @@ -122,6 +138,7 @@ export default { ) }, + // 渲染子菜单 renderSubMenu (menu) { const itemArr = [] if (!menu.hideChildrenInMenu) { @@ -137,6 +154,7 @@ export default { ) }, + // 渲染图标 renderIcon (icon) { if (icon === 'none' || icon === undefined) { return null @@ -177,4 +195,4 @@ export default { ) } -} +} \ No newline at end of file diff --git a/frontend/src/components/_util/util.js b/frontend/src/components/_util/util.js index dd33231..651534e 100644 --- a/frontend/src/components/_util/util.js +++ b/frontend/src/components/_util/util.js @@ -7,7 +7,9 @@ * @param children * @returns {*[]} */ +// 导出一个函数,用于过滤掉空节点 export function filterEmpty (children = []) { + // 过滤掉没有tag属性和text属性为空字符串的节点 return children.filter(c => c.tag || (c.text && c.text.trim() !== '')) } @@ -15,12 +17,17 @@ export function filterEmpty (children = []) { * 获取字符串长度,英文字符 长度1,中文字符长度2 * @param {*} str */ +// 导出一个函数,用于获取字符串长度 export const getStrFullLength = (str = '') => + // 将字符串分割成字符数组,然后使用reduce方法遍历数组,计算每个字符的长度 str.split('').reduce((pre, cur) => { + // 获取字符的Unicode编码 const charCode = cur.charCodeAt(0) + // 如果字符的Unicode编码在0-128之间,说明是英文字符,长度为1 if (charCode >= 0 && charCode <= 128) { return pre + 1 } + // 否则,说明是中文字符,长度为2 return pre + 2 }, 0) @@ -29,18 +36,26 @@ export const getStrFullLength = (str = '') => * @param {*} str * @param {*} maxLength */ +// 导出一个函数,用于截取字符串 export const cutStrByFullLength = (str = '', maxLength) => { + // 初始化显示长度为0 let showLength = 0 + // 将字符串分割成字符数组,然后使用reduce方法遍历数组,截取字符串 return str.split('').reduce((pre, cur) => { + // 获取字符的Unicode编码 const charCode = cur.charCodeAt(0) + // 如果字符的Unicode编码在0-128之间,说明是英文字符,长度为1 if (charCode >= 0 && charCode <= 128) { showLength += 1 } else { + // 否则,说明是中文字符,长度为2 showLength += 2 } + // 如果显示长度小于等于maxLength,则将字符添加到结果中 if (showLength <= maxLength) { return pre + cur } + // 否则,返回结果 return pre }, '') } diff --git a/frontend/src/views/account/settings/AvatarModal.vue b/frontend/src/views/account/settings/AvatarModal.vue index 0ad9c41..6c0f31c 100644 --- a/frontend/src/views/account/settings/AvatarModal.vue +++ b/frontend/src/views/account/settings/AvatarModal.vue @@ -6,8 +6,10 @@ :confirmLoading="confirmLoading" :width="800" @cancel="cancelHandel"> + + +
+