Przeglądaj źródła

Merge branch 'iterator5'

ChenSiTong 6 lat temu
rodzic
commit
d47e096572
38 zmienionych plików z 500 dodań i 231 usunięć
  1. 1 1
      pom.xml
  2. 3 3
      src/main/java/nju/seec/helper/aspect/event/EventAspect.java
  3. 12 12
      src/main/java/nju/seec/helper/aspect/record/ExerciseRecord.java
  4. 79 79
      src/main/java/nju/seec/helper/aspect/record/ExerciseRecordAspect.java
  5. 2 2
      src/main/java/nju/seec/helper/controller/CommentController.java
  6. 12 2
      src/main/java/nju/seec/helper/controller/CourseController.java
  7. 1 1
      src/main/java/nju/seec/helper/controller/CourseFileController.java
  8. 1 1
      src/main/java/nju/seec/helper/controller/EventController.java
  9. 32 4
      src/main/java/nju/seec/helper/controller/QuestionController.java
  10. 6 7
      src/main/java/nju/seec/helper/controller/QuizController.java
  11. 1 1
      src/main/java/nju/seec/helper/controller/RecordController.java
  12. 0 1
      src/main/java/nju/seec/helper/dao/MessageDAO.java
  13. 3 0
      src/main/java/nju/seec/helper/dto/course/CourseDTO.java
  14. 15 0
      src/main/java/nju/seec/helper/dto/course/CourseKnowledgeDTO.java
  15. 1 1
      src/main/java/nju/seec/helper/dto/coursefile/CourseFileDTO.java
  16. 7 5
      src/main/java/nju/seec/helper/dto/question/StudentAnswerDTO.java
  17. 5 0
      src/main/java/nju/seec/helper/entity/Course.java
  18. 2 1
      src/main/java/nju/seec/helper/entity/QuizStudentAnswer.java
  19. 1 1
      src/main/java/nju/seec/helper/service/CourseFileService.java
  20. 10 0
      src/main/java/nju/seec/helper/service/CourseService.java
  21. 34 7
      src/main/java/nju/seec/helper/service/QuestionService.java
  22. 3 3
      src/main/java/nju/seec/helper/service/QuizStudentAnswerService.java
  23. 1 1
      src/main/java/nju/seec/helper/service/impl/CourseFileServiceImpl.java
  24. 33 20
      src/main/java/nju/seec/helper/service/impl/CourseServiceImpl.java
  25. 19 36
      src/main/java/nju/seec/helper/service/impl/EventServiceImpl.java
  26. 76 19
      src/main/java/nju/seec/helper/service/impl/QuestionServiceImpl.java
  27. 1 1
      src/main/java/nju/seec/helper/service/impl/QuizServiceImpl.java
  28. 19 11
      src/main/java/nju/seec/helper/service/impl/QuizStudentAnswerServiceImpl.java
  29. 2 2
      src/main/java/nju/seec/helper/service/impl/RecordServiceImpl.java
  30. 1 1
      src/main/java/nju/seec/helper/sys/dataanalysis/api/ExerciseApi.java
  31. 32 0
      src/main/java/nju/seec/helper/sys/ml/api/RecommendationApi.java
  32. 15 0
      src/main/java/nju/seec/helper/sys/ml/vo/RecommendResultVO.java
  33. 11 2
      src/main/java/nju/seec/helper/util/StringUtils.java
  34. 5 0
      src/main/java/nju/seec/helper/vo/CourseVO.java
  35. 2 0
      src/main/java/nju/seec/helper/vo/question/BaseQuestionVO.java
  36. 36 0
      src/main/java/nju/seec/helper/vo/question/MultipleChoiceQuestionVO.java
  37. 8 3
      src/main/resources/application-deploy.yml
  38. 8 3
      src/main/resources/application-dev.yml

+ 1 - 1
pom.xml

@@ -10,7 +10,7 @@
     </parent>
     <groupId>cn.seecoder</groupId>
     <artifactId>helper-backend</artifactId>
-    <version>5.3.0</version>
+    <version>5.4.0</version>
     <packaging>jar</packaging>
     <name>helper</name>
     <description>backend</description>

+ 3 - 3
src/main/java/nju/seec/helper/aspect/event/EventAspect.java

@@ -2,13 +2,13 @@ package nju.seec.helper.aspect.event;
 
 import com.google.common.collect.ImmutableMap;
 import lombok.SneakyThrows;
-import nju.seec.helper.sys.dataanalysis.api.EventApi;
-import nju.seec.helper.sys.dataanalysis.constant.EventAction;
-import nju.seec.helper.sys.dataanalysis.constant.EventType;
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.CourseDAO;
 import nju.seec.helper.dao.QuizDAO;
 import nju.seec.helper.dto.comment.CommentDTO;
+import nju.seec.helper.sys.dataanalysis.api.EventApi;
+import nju.seec.helper.sys.dataanalysis.constant.EventAction;
+import nju.seec.helper.sys.dataanalysis.constant.EventType;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.annotation.AfterReturning;
 import org.aspectj.lang.annotation.Aspect;

+ 12 - 12
src/main/java/nju/seec/helper/aspect/record/ExerciseRecord.java

@@ -1,12 +1,12 @@
-package nju.seec.helper.aspect.record;
-
-import java.lang.annotation.*;
-
-/**
- * @author cst
- */
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-public @interface ExerciseRecord {
-}
+//package nju.seec.helper.aspect.record;
+//
+//import java.lang.annotation.*;
+//
+///**
+// * @author cst
+// */
+//@Target(ElementType.METHOD)
+//@Retention(RetentionPolicy.RUNTIME)
+//@Documented
+//public @interface ExerciseRecord {
+//}

+ 79 - 79
src/main/java/nju/seec/helper/aspect/record/ExerciseRecordAspect.java

@@ -1,79 +1,79 @@
-package nju.seec.helper.aspect.record;
-
-import nju.seec.helper.sys.bok.api.RecordApi;
-import nju.seec.helper.sys.dataanalysis.api.ExerciseApi;
-import nju.seec.helper.aspect.auth.LoginUser;
-import nju.seec.helper.dto.quiz.QuizStudentAnswerDTO;
-import nju.seec.helper.vo.quiz.QuizStudentAnswerVO;
-import org.aspectj.lang.annotation.AfterReturning;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Pointcut;
-import org.springframework.stereotype.Component;
-
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-/**
- * @author cst
- */
-@Aspect
-@Component
-public class ExerciseRecordAspect {
-    private final ExerciseApi exerciseApi;
-    private final RecordApi recordApi;
-
-    public ExerciseRecordAspect(ExerciseApi exerciseApi, RecordApi recordApi) {
-        this.exerciseApi = exerciseApi;
-        this.recordApi = recordApi;
-    }
-
-    @Pointcut(value = "@annotation(exerciseRecord) && args(user,..,quizStudentAnswerDTO)", argNames = "exerciseRecord,user,quizStudentAnswerDTO")
-    public void pointCut(ExerciseRecord exerciseRecord, LoginUser user, QuizStudentAnswerDTO quizStudentAnswerDTO) {
-
-    }
-
-    @AfterReturning(value = "pointCut(exerciseRecord,user,quizStudentAnswerDTO)", returning = "quizStudentAnswerVO", argNames = "exerciseRecord,user,quizStudentAnswerDTO,quizStudentAnswerVO")
-    public void reportRecordToData(ExerciseRecord exerciseRecord, LoginUser user, QuizStudentAnswerDTO quizStudentAnswerDTO, QuizStudentAnswerVO quizStudentAnswerVO) {
-        String userId = String.valueOf(user.getPid());
-        Map<String, QuizStudentAnswerVO.QuestionStudentAnswerInfo> questionStudentAnswerInfoMap = quizStudentAnswerVO.getAnswers();
-        exerciseApi.reportExercise(
-                quizStudentAnswerDTO
-                        .getRecords()
-                        .entrySet()
-                        .stream()
-                        .map(answerRecordDTOEntry -> {
-                            String questionId = answerRecordDTOEntry.getKey();
-                            QuizStudentAnswerDTO.AnswerRecordDTO answerRecordDTO = answerRecordDTOEntry.getValue();
-                            QuizStudentAnswerVO.QuestionStudentAnswerInfo studentAnswerInfo = questionStudentAnswerInfoMap.get(questionId);
-                            return studentAnswerInfo != null
-                                    ? exerciseApi.createExerciseRecordDTO(userId, questionId, answerRecordDTO.getStartAt(), answerRecordDTO.getEndAt(), studentAnswerInfo.getCorrect())
-                                    : null;
-                        })
-                        .filter(Objects::nonNull)
-                        .collect(Collectors.toList())
-        );
-    }
-
-    @AfterReturning(value = "pointCut(exerciseRecord,user,quizStudentAnswerDTO)", returning = "quizStudentAnswerVO", argNames = "exerciseRecord,user,quizStudentAnswerDTO,quizStudentAnswerVO")
-    public void reportRecordToBok(ExerciseRecord exerciseRecord, LoginUser user, QuizStudentAnswerDTO quizStudentAnswerDTO, QuizStudentAnswerVO quizStudentAnswerVO) {
-        String userId = String.valueOf(user.getPid());
-        Map<String, QuizStudentAnswerVO.QuestionStudentAnswerInfo> questionStudentAnswerInfoMap = quizStudentAnswerVO.getAnswers();
-        recordApi.reportUserRecord(
-                quizStudentAnswerDTO
-                        .getRecords()
-                        .entrySet()
-                        .stream()
-                        .map(answerRecordDTOEntry -> {
-                            String questionId = answerRecordDTOEntry.getKey();
-                            QuizStudentAnswerDTO.AnswerRecordDTO answerRecordDTO = answerRecordDTOEntry.getValue();
-                            QuizStudentAnswerVO.QuestionStudentAnswerInfo studentAnswerInfo = questionStudentAnswerInfoMap.get(questionId);
-                            return studentAnswerInfo != null
-                                    ? recordApi.createRecordDTO(userId, questionId, studentAnswerInfo.getAnswer(), studentAnswerInfo.getCorrect(), answerRecordDTO.getStartAt(), answerRecordDTO.getEndAt())
-                                    : null;
-                        })
-                        .filter(Objects::nonNull)
-                        .collect(Collectors.toList())
-        );
-    }
-}
+//package nju.seec.helper.aspect.record;
+//
+//import nju.seec.helper.sys.bok.api.RecordApi;
+//import nju.seec.helper.sys.dataanalysis.api.ExerciseApi;
+//import nju.seec.helper.aspect.auth.LoginUser;
+//import nju.seec.helper.dto.question.StudentAnswerDTO;
+//import nju.seec.helper.vo.quiz.QuizStudentAnswerVO;
+//import org.aspectj.lang.annotation.AfterReturning;
+//import org.aspectj.lang.annotation.Aspect;
+//import org.aspectj.lang.annotation.Pointcut;
+//import org.springframework.stereotype.Component;
+//
+//import java.util.Map;
+//import java.util.Objects;
+//import java.util.stream.Collectors;
+//
+///**
+// * @author cst
+// */
+//@Aspect
+//@Component
+//public class ExerciseRecordAspect {
+//    private final ExerciseApi exerciseApi;
+//    private final RecordApi recordApi;
+//
+//    public ExerciseRecordAspect(ExerciseApi exerciseApi, RecordApi recordApi) {
+//        this.exerciseApi = exerciseApi;
+//        this.recordApi = recordApi;
+//    }
+//
+//    @Pointcut(value = "@annotation(exerciseRecord) && args(user,..,studentAnswerDTO)", argNames = "exerciseRecord,user,quizStudentAnswerDTO")
+//    public void pointCut(ExerciseRecord exerciseRecord, LoginUser user, StudentAnswerDTO studentAnswerDTO) {
+//
+//    }
+//
+//    @AfterReturning(value = "pointCut(exerciseRecord,user,studentAnswerDTO)", returning = "quizStudentAnswerVO", argNames = "exerciseRecord,user,quizStudentAnswerDTO,quizStudentAnswerVO")
+//    public void reportRecordToData(ExerciseRecord exerciseRecord, LoginUser user, StudentAnswerDTO studentAnswerDTO, QuizStudentAnswerVO quizStudentAnswerVO) {
+//        String userId = String.valueOf(user.getPid());
+//        Map<String, QuizStudentAnswerVO.QuestionStudentAnswerInfo> questionStudentAnswerInfoMap = quizStudentAnswerVO.getAnswers();
+//        exerciseApi.reportExercise(
+//                studentAnswerDTO
+//                        .getRecords()
+//                        .entrySet()
+//                        .stream()
+//                        .map(answerRecordDTOEntry -> {
+//                            String questionId = answerRecordDTOEntry.getKey();
+//                            StudentAnswerDTO.AnswerRecordDTO answerRecordDTO = answerRecordDTOEntry.getValue();
+//                            QuizStudentAnswerVO.QuestionStudentAnswerInfo studentAnswerInfo = questionStudentAnswerInfoMap.get(questionId);
+//                            return studentAnswerInfo != null
+//                                    ? exerciseApi.createExerciseRecordDTO(userId, questionId, answerRecordDTO.getStartAt(), answerRecordDTO.getEndAt(), studentAnswerInfo.getCorrect())
+//                                    : null;
+//                        })
+//                        .filter(Objects::nonNull)
+//                        .collect(Collectors.toList())
+//        );
+//    }
+//
+//    @AfterReturning(value = "pointCut(exerciseRecord,user,studentAnswerDTO)", returning = "quizStudentAnswerVO", argNames = "exerciseRecord,user,quizStudentAnswerDTO,quizStudentAnswerVO")
+//    public void reportRecordToBok(ExerciseRecord exerciseRecord, LoginUser user, StudentAnswerDTO studentAnswerDTO, QuizStudentAnswerVO quizStudentAnswerVO) {
+//        String userId = String.valueOf(user.getPid());
+//        Map<String, QuizStudentAnswerVO.QuestionStudentAnswerInfo> questionStudentAnswerInfoMap = quizStudentAnswerVO.getAnswers();
+//        recordApi.reportUserRecord(
+//                studentAnswerDTO
+//                        .getRecords()
+//                        .entrySet()
+//                        .stream()
+//                        .map(answerRecordDTOEntry -> {
+//                            String questionId = answerRecordDTOEntry.getKey();
+//                            StudentAnswerDTO.AnswerRecordDTO answerRecordDTO = answerRecordDTOEntry.getValue();
+//                            QuizStudentAnswerVO.QuestionStudentAnswerInfo studentAnswerInfo = questionStudentAnswerInfoMap.get(questionId);
+//                            return studentAnswerInfo != null
+//                                    ? recordApi.createRecordDTO(userId, questionId, studentAnswerInfo.getAnswer(), studentAnswerInfo.getCorrect(), answerRecordDTO.getStartAt(), answerRecordDTO.getEndAt())
+//                                    : null;
+//                        })
+//                        .filter(Objects::nonNull)
+//                        .collect(Collectors.toList())
+//        );
+//    }
+//}

+ 2 - 2
src/main/java/nju/seec/helper/controller/CommentController.java

@@ -1,7 +1,5 @@
 package nju.seec.helper.controller;
 
-import nju.seec.helper.sys.dataanalysis.constant.EventAction;
-import nju.seec.helper.sys.dataanalysis.constant.EventType;
 import nju.seec.helper.aspect.auth.Auth;
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.aspect.event.Event;
@@ -9,6 +7,8 @@ import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.comment.CommentDTO;
 import nju.seec.helper.enums.UserRole;
 import nju.seec.helper.service.CommentService;
+import nju.seec.helper.sys.dataanalysis.constant.EventAction;
+import nju.seec.helper.sys.dataanalysis.constant.EventType;
 import nju.seec.helper.util.JsonUtils;
 import nju.seec.helper.vo.CommentVO;
 import org.springframework.data.domain.Pageable;

+ 12 - 2
src/main/java/nju/seec/helper/controller/CourseController.java

@@ -1,17 +1,18 @@
 package nju.seec.helper.controller;
 
-import nju.seec.helper.sys.dataanalysis.constant.EventAction;
-import nju.seec.helper.sys.dataanalysis.constant.EventType;
 import nju.seec.helper.aspect.auth.Auth;
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.aspect.event.Event;
 import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.course.ChooseDTO;
 import nju.seec.helper.dto.course.CourseDTO;
+import nju.seec.helper.dto.course.CourseKnowledgeDTO;
 import nju.seec.helper.dto.groups.Create;
 import nju.seec.helper.dto.groups.Modify;
 import nju.seec.helper.enums.UserRole;
 import nju.seec.helper.service.CourseService;
+import nju.seec.helper.sys.dataanalysis.constant.EventAction;
+import nju.seec.helper.sys.dataanalysis.constant.EventType;
 import nju.seec.helper.vo.CourseVO;
 import nju.seec.helper.vo.UserVO;
 import org.springframework.data.domain.Pageable;
@@ -64,6 +65,15 @@ public class CourseController {
         return courseService.modifyCourse(user, courseId, courseDTO);
     }
 
+    /**
+     * 教师修改课程知识点
+     */
+    @Auth(roles = UserRole.TEACHER, message = "修改课程知识点")
+    @PutMapping("/{courseId}/knowledge")
+    public void modifyCourseKnowledgeId(LoginUser user, @PathVariable Long courseId, @Validated @RequestBody CourseKnowledgeDTO courseKnowledgeDTO) {
+        courseService.modifyCourseKnowledgeId(user, courseId, courseKnowledgeDTO);
+    }
+
     /**
      * 学生选课
      */

+ 1 - 1
src/main/java/nju/seec/helper/controller/CourseFileController.java

@@ -3,7 +3,7 @@ package nju.seec.helper.controller;
 import nju.seec.helper.aspect.auth.Auth;
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.controller.response.PageResponse;
-import nju.seec.helper.dto.course.CourseFileDTO;
+import nju.seec.helper.dto.coursefile.CourseFileDTO;
 import nju.seec.helper.enums.UserRole;
 import nju.seec.helper.service.CourseFileService;
 import nju.seec.helper.vo.CourseFileVO;

+ 1 - 1
src/main/java/nju/seec/helper/controller/EventController.java

@@ -1,7 +1,7 @@
 package nju.seec.helper.controller;
 
-import nju.seec.helper.sys.dataanalysis.model.Event;
 import nju.seec.helper.service.EventService;
+import nju.seec.helper.sys.dataanalysis.model.Event;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;

+ 32 - 4
src/main/java/nju/seec/helper/controller/QuestionController.java

@@ -1,16 +1,17 @@
 package nju.seec.helper.controller;
 
-import nju.seec.helper.sys.bok.dto.CollectDTO;
-import nju.seec.helper.sys.bok.dto.RateDTO;
-import nju.seec.helper.sys.bok.vo.InteractiveVO;
 import nju.seec.helper.aspect.auth.Auth;
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.groups.Create;
 import nju.seec.helper.dto.groups.Modify;
 import nju.seec.helper.dto.question.BaseQuestionDTO;
+import nju.seec.helper.dto.question.StudentAnswerDTO;
 import nju.seec.helper.enums.UserRole;
 import nju.seec.helper.service.QuestionService;
+import nju.seec.helper.sys.bok.dto.CollectDTO;
+import nju.seec.helper.sys.bok.dto.RateDTO;
+import nju.seec.helper.sys.bok.vo.InteractiveVO;
 import nju.seec.helper.vo.question.BaseQuestionVO;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.web.PageableDefault;
@@ -55,7 +56,7 @@ public class QuestionController {
     @Auth(roles = {UserRole.TEACHER, UserRole.STUDENT}, message = "获得测试题目")
     @GetMapping("/quiz/{quizId}")
     public List<BaseQuestionVO> getQuestionsByQuiz(LoginUser user, @PathVariable Long quizId) {
-        return questionService.getQuestionsByQuiz(user, quizId);
+        return questionService.getQuestionsByQuizId(user, quizId);
     }
 
     /**
@@ -162,4 +163,31 @@ public class QuestionController {
     public List<InteractiveVO> getInteractiveList(LoginUser user, String questionId) {
         return questionService.getInteractiveList(user, questionId);
     }
+
+    /**
+     * 取得推荐题目
+     */
+    @Auth(roles = UserRole.STUDENT, message = "取得推荐题目")
+    @GetMapping("/recommendation")
+    public List<BaseQuestionVO> getRecommendationQuestions(LoginUser user, @RequestParam(required = false) List<String> knowledgeId, @RequestParam(required = false, defaultValue = "5") Integer questionNum) {
+        return questionService.getRecommendationQuestions(user, knowledgeId, questionNum);
+    }
+
+    /**
+     * 根据课程取得推荐题目
+     */
+    @Auth(roles = UserRole.STUDENT, message = "根据课程取得推荐题目")
+    @GetMapping("/course/{courseId}/recommendation")
+    public List<BaseQuestionVO> getRecommendationQuestionsByCourse(LoginUser user, @PathVariable Long courseId, @RequestParam(required = false, defaultValue = "5") Integer questionNum) {
+        return questionService.getRecommendationQuestionsByCourse(user, courseId, questionNum);
+    }
+
+    /**
+     * 提交做题情况
+     */
+    @Auth(roles = UserRole.STUDENT, message = "提交做题情况")
+    @PostMapping("/record")
+    public void submitAnswerRecord(LoginUser user, @Validated @RequestBody StudentAnswerDTO studentAnswerDTO) {
+        questionService.record(user, studentAnswerDTO);
+    }
 }

+ 6 - 7
src/main/java/nju/seec/helper/controller/QuizController.java

@@ -1,20 +1,19 @@
 package nju.seec.helper.controller;
 
-import nju.seec.helper.sys.dataanalysis.constant.EventAction;
-import nju.seec.helper.sys.dataanalysis.constant.EventType;
 import nju.seec.helper.aspect.auth.Auth;
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.aspect.event.Event;
-import nju.seec.helper.aspect.record.ExerciseRecord;
 import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.groups.Create;
 import nju.seec.helper.dto.groups.Modify;
+import nju.seec.helper.dto.question.StudentAnswerDTO;
 import nju.seec.helper.dto.quiz.QuizDTO;
-import nju.seec.helper.dto.quiz.QuizStudentAnswerDTO;
 import nju.seec.helper.enums.QuizState;
 import nju.seec.helper.enums.UserRole;
 import nju.seec.helper.service.QuizService;
 import nju.seec.helper.service.QuizStudentAnswerService;
+import nju.seec.helper.sys.dataanalysis.constant.EventAction;
+import nju.seec.helper.sys.dataanalysis.constant.EventType;
 import nju.seec.helper.vo.quiz.QuizStudentAnswerStatisticVO;
 import nju.seec.helper.vo.quiz.QuizStudentAnswerVO;
 import nju.seec.helper.vo.quiz.QuizVO;
@@ -118,12 +117,12 @@ public class QuizController {
     /**
      * 提交作答
      */
-    @ExerciseRecord
+//    @ExerciseRecord
     @Event(type = EventType.QUIZ, action = EventAction.COMPLETE)
     @Auth(roles = {UserRole.STUDENT}, message = "提交作答")
     @PostMapping("/{quizId}/student-answer")
-    public QuizStudentAnswerVO submitQuizStudentAnswer(LoginUser user, @PathVariable Long quizId, @Validated @RequestBody QuizStudentAnswerDTO quizStudentAnswerDTO) {
-        return quizStudentAnswerService.submitQuizStudentAnswer(user, quizId, quizStudentAnswerDTO);
+    public QuizStudentAnswerVO submitQuizStudentAnswer(LoginUser user, @PathVariable Long quizId, @Validated @RequestBody StudentAnswerDTO studentAnswerDTO) {
+        return quizStudentAnswerService.submitQuizStudentAnswer(user, quizId, studentAnswerDTO);
     }
 
     /**

+ 1 - 1
src/main/java/nju/seec/helper/controller/RecordController.java

@@ -1,7 +1,7 @@
 package nju.seec.helper.controller;
 
-import nju.seec.helper.sys.dataanalysis.model.ExerciseRecord;
 import nju.seec.helper.service.RecordService;
+import nju.seec.helper.sys.dataanalysis.model.ExerciseRecord;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;

+ 0 - 1
src/main/java/nju/seec/helper/dao/MessageDAO.java

@@ -1,7 +1,6 @@
 package nju.seec.helper.dao;
 
 import nju.seec.helper.entity.Message;
-import nju.seec.helper.enums.MessageType;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;

+ 3 - 0
src/main/java/nju/seec/helper/dto/course/CourseDTO.java

@@ -6,6 +6,7 @@ import org.hibernate.validator.constraints.Length;
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
 import java.io.Serializable;
+import java.util.List;
 
 /**
  * @author cst
@@ -22,4 +23,6 @@ public class CourseDTO implements Serializable {
     @NotBlank
     @Length(max = 50, message = "选课码长度不能超过50")
     private String code;
+
+    private List<String> knowledgeId;
 }

+ 15 - 0
src/main/java/nju/seec/helper/dto/course/CourseKnowledgeDTO.java

@@ -0,0 +1,15 @@
+package nju.seec.helper.dto.course;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+
+/**
+ * @author cst
+ */
+@Data
+public class CourseKnowledgeDTO {
+    @NotEmpty(message = "知识点列表不能为空")
+    private List<String> knowledgeId;
+}

+ 1 - 1
src/main/java/nju/seec/helper/dto/course/CourseFileDTO.java → src/main/java/nju/seec/helper/dto/coursefile/CourseFileDTO.java

@@ -1,4 +1,4 @@
-package nju.seec.helper.dto.course;
+package nju.seec.helper.dto.coursefile;
 
 import lombok.Data;
 import org.springframework.web.multipart.MultipartFile;

+ 7 - 5
src/main/java/nju/seec/helper/dto/quiz/QuizStudentAnswerDTO.java → src/main/java/nju/seec/helper/dto/question/StudentAnswerDTO.java

@@ -1,9 +1,8 @@
-package nju.seec.helper.dto.quiz;
+package nju.seec.helper.dto.question;
 
 import lombok.Data;
 
 import javax.validation.Valid;
-import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Past;
 import java.io.Serializable;
@@ -14,10 +13,10 @@ import java.util.Map;
  * @author cst
  */
 @Data
-public class QuizStudentAnswerDTO implements Serializable {
+public class StudentAnswerDTO implements Serializable {
     private static final long serialVersionUID = -5846899734313166305L;
-    @NotEmpty(message = "回答不能为空")
-    private Map<String, Object> answers;
+//    @NotEmpty(message = "回答不能为空")
+//    private Map<String, Object> answers;
 
     @NotNull(message = "缺少做题记录")
     @Valid
@@ -25,6 +24,9 @@ public class QuizStudentAnswerDTO implements Serializable {
 
     @Data
     public static class AnswerRecordDTO {
+        @NotNull(message = "缺少答案")
+        private Object answer;
+
         @NotNull(message = "缺少做题开始时间")
         @Past(message = "开始时间错误")
         private LocalDateTime startAt;

+ 5 - 0
src/main/java/nju/seec/helper/entity/Course.java

@@ -10,6 +10,7 @@ import javax.persistence.ForeignKey;
 import javax.persistence.Table;
 import javax.persistence.*;
 import java.time.LocalDateTime;
+import java.util.List;
 
 /**
  * @author cst
@@ -48,4 +49,8 @@ public class Course {
     @ColumnDefault("0")
     @Column(name = "delete_at", nullable = false)
     private Long deleteAt = 0L;
+
+    @Type(type = "json")
+    @Column(columnDefinition = "json", name = "knowledge_id")
+    private List<String> knowledgeId;
 }

+ 2 - 1
src/main/java/nju/seec/helper/entity/QuizStudentAnswer.java

@@ -12,6 +12,7 @@ import javax.persistence.Table;
 import javax.persistence.*;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -41,7 +42,7 @@ public class QuizStudentAnswer {
 
     @Type(type = "json")
     @Column(columnDefinition = "json", nullable = false)
-    private Map<String, Object> answers;
+    private Map<String, Object> answers = new HashMap<>();
 
     @Column(name = "submit_at", nullable = false)
     @UpdateTimestamp

+ 1 - 1
src/main/java/nju/seec/helper/service/CourseFileService.java

@@ -1,7 +1,7 @@
 package nju.seec.helper.service;
 
 import nju.seec.helper.aspect.auth.LoginUser;
-import nju.seec.helper.dto.course.CourseFileDTO;
+import nju.seec.helper.dto.coursefile.CourseFileDTO;
 import nju.seec.helper.entity.CourseFile;
 import nju.seec.helper.vo.CourseFileVO;
 import org.springframework.data.domain.Page;

+ 10 - 0
src/main/java/nju/seec/helper/service/CourseService.java

@@ -3,6 +3,7 @@ package nju.seec.helper.service;
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.course.ChooseDTO;
 import nju.seec.helper.dto.course.CourseDTO;
+import nju.seec.helper.dto.course.CourseKnowledgeDTO;
 import nju.seec.helper.entity.Course;
 import nju.seec.helper.vo.CourseVO;
 import nju.seec.helper.vo.UserVO;
@@ -32,6 +33,15 @@ public interface CourseService extends AuthService<Course> {
      */
     CourseVO modifyCourse(LoginUser user, Long courseId, CourseDTO courseDTO);
 
+    /**
+     * 修改课程知识点
+     *
+     * @param user
+     * @param courseId
+     * @param courseKnowledgeDTO
+     */
+    void modifyCourseKnowledgeId(LoginUser user, Long courseId, CourseKnowledgeDTO courseKnowledgeDTO);
+
     /**
      * 删除课程(伪删除)
      *

+ 34 - 7
src/main/java/nju/seec/helper/service/QuestionService.java

@@ -1,11 +1,11 @@
 package nju.seec.helper.service;
 
+import nju.seec.helper.aspect.auth.LoginUser;
+import nju.seec.helper.dto.question.BaseQuestionDTO;
+import nju.seec.helper.dto.question.StudentAnswerDTO;
 import nju.seec.helper.sys.bok.dto.CollectDTO;
 import nju.seec.helper.sys.bok.dto.RateDTO;
 import nju.seec.helper.sys.bok.vo.InteractiveVO;
-import nju.seec.helper.aspect.auth.LoginUser;
-import nju.seec.helper.dto.question.BaseQuestionDTO;
-import nju.seec.helper.entity.Quiz;
 import nju.seec.helper.vo.question.BaseQuestionVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -37,15 +37,14 @@ public interface QuestionService {
      * @param quizId
      * @return
      */
-    List<BaseQuestionVO> getQuestionsByQuiz(LoginUser user, Long quizId);
+    List<BaseQuestionVO> getQuestionsByQuizId(LoginUser user, Long quizId);
 
     /**
      * 根据测试获取题目(无需权限检验)
      *
-     * @param quiz
-     * @return
+     * @param questionIds@return
      */
-    List<BaseQuestionVO> getQuestionsByQuiz(Quiz quiz);
+    List<BaseQuestionVO> getQuestionsByIds(List<String> questionIds);
 
     /**
      * 获取某一题目
@@ -146,4 +145,32 @@ public interface QuestionService {
      * @return
      */
     List<InteractiveVO> getInteractiveList(LoginUser user, String questionId);
+
+    /**
+     * 取得推荐题目
+     *
+     * @param user
+     * @param knowledgeId
+     * @param questionNum
+     * @return
+     */
+    List<BaseQuestionVO> getRecommendationQuestions(LoginUser user, List<String> knowledgeId, Integer questionNum);
+
+    /**
+     * 记录做题情况
+     *
+     * @param user
+     * @param studentAnswerDTO
+     */
+    void record(LoginUser user, StudentAnswerDTO studentAnswerDTO);
+
+    /**
+     * 感觉课程取得推荐题目
+     *
+     * @param user
+     * @param courseId
+     * @param questionNum
+     * @return
+     */
+    List<BaseQuestionVO> getRecommendationQuestionsByCourse(LoginUser user, Long courseId, Integer questionNum);
 }

+ 3 - 3
src/main/java/nju/seec/helper/service/QuizStudentAnswerService.java

@@ -1,7 +1,7 @@
 package nju.seec.helper.service;
 
 import nju.seec.helper.aspect.auth.LoginUser;
-import nju.seec.helper.dto.quiz.QuizStudentAnswerDTO;
+import nju.seec.helper.dto.question.StudentAnswerDTO;
 import nju.seec.helper.vo.quiz.QuizStudentAnswerStatisticVO;
 import nju.seec.helper.vo.quiz.QuizStudentAnswerVO;
 import org.springframework.data.domain.Page;
@@ -16,10 +16,10 @@ public interface QuizStudentAnswerService {
      *
      * @param user
      * @param quizId
-     * @param quizStudentAnswerDTO
+     * @param studentAnswerDTO
      * @return
      */
-    QuizStudentAnswerVO submitQuizStudentAnswer(LoginUser user, Long quizId, QuizStudentAnswerDTO quizStudentAnswerDTO);
+    QuizStudentAnswerVO submitQuizStudentAnswer(LoginUser user, Long quizId, StudentAnswerDTO studentAnswerDTO);
 
     /**
      * 老师查看学生答案列表

+ 1 - 1
src/main/java/nju/seec/helper/service/impl/CourseFileServiceImpl.java

@@ -5,7 +5,7 @@ import lombok.SneakyThrows;
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.CourseDAO;
 import nju.seec.helper.dao.CourseFileDAO;
-import nju.seec.helper.dto.course.CourseFileDTO;
+import nju.seec.helper.dto.coursefile.CourseFileDTO;
 import nju.seec.helper.entity.Course;
 import nju.seec.helper.entity.CourseFile;
 import nju.seec.helper.enums.ExceptionType;

+ 33 - 20
src/main/java/nju/seec/helper/service/impl/CourseServiceImpl.java

@@ -6,6 +6,7 @@ import nju.seec.helper.dao.CourseDAO;
 import nju.seec.helper.dao.UserDAO;
 import nju.seec.helper.dto.course.ChooseDTO;
 import nju.seec.helper.dto.course.CourseDTO;
+import nju.seec.helper.dto.course.CourseKnowledgeDTO;
 import nju.seec.helper.entity.Choose;
 import nju.seec.helper.entity.Course;
 import nju.seec.helper.enums.ExceptionType;
@@ -39,14 +40,14 @@ public class CourseServiceImpl implements CourseService {
     @Override
     public CourseVO createCourse(LoginUser teacher, CourseDTO courseDTO) {
         try {
-            return new CourseVO(
-                    courseDAO.save(
-                            new Course()
-                                    .setTeacher(userDAO.findUserById(teacher.getId()))
-                                    .setName(courseDTO.getName())
-                                    .setBio(courseDTO.getBio())
-                                    .setCode(courseDTO.getCode()))
-            );
+            Course course = new Course()
+                    .setTeacher(userDAO.findUserById(teacher.getId()))
+                    .setName(courseDTO.getName())
+                    .setBio(courseDTO.getBio())
+                    .setCode(courseDTO.getCode())
+                    .setKnowledgeId(courseDTO.getKnowledgeId());
+
+            return new CourseVO(courseDAO.save(course));
         } catch (DataIntegrityViolationException e) {
             throw HelperException.of(ExceptionType.CONFLICT, "创建失败,请检查课程信息是否正确且不与已有课程冲突");
         }
@@ -61,19 +62,31 @@ public class CourseServiceImpl implements CourseService {
             throw HelperException.of(ExceptionType.FORBIDDEN, "您无权修改该课程");
         }
 
+        course.setName(courseDTO.getName())
+                .setBio(courseDTO.getBio())
+                .setCode(courseDTO.getCode());
+
         try {
-            return new CourseVO(
-                    courseDAO.save(
-                            course.setName(courseDTO.getName())
-                                    .setBio(courseDTO.getBio())
-                                    .setCode(courseDTO.getCode())
-                    )
-            );
+            return new CourseVO(courseDAO.save(course));
         } catch (DataIntegrityViolationException e) {
             throw HelperException.of(ExceptionType.CONFLICT, "修改失败,请检查课程信息是否正确且不与已有课程冲突");
         }
     }
 
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void modifyCourseKnowledgeId(LoginUser user, Long courseId, CourseKnowledgeDTO courseKnowledgeDTO) {
+        Course course = courseDAO.findCourseById(courseId);
+
+        if (!existsUpdateAuth(user, course)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权修改该课程知识点");
+        }
+
+        course.setKnowledgeId(courseKnowledgeDTO.getKnowledgeId());
+
+        courseDAO.save(course);
+    }
+
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void deleteCourse(LoginUser user, Long courseId) {
@@ -100,11 +113,11 @@ public class CourseServiceImpl implements CourseService {
             throw HelperException.of(ExceptionType.NOT_FOUND, "选课码错误");
         }
 
-        chooseDAO.save(
-                new Choose()
-                        .setCourseId(courseId)
-                        .setStudentId(studentId)
-        );
+        Choose choose = new Choose()
+                .setCourseId(courseId)
+                .setStudentId(studentId);
+
+        chooseDAO.save(choose);
     }
 
     @Transactional(rollbackFor = Exception.class)

+ 19 - 36
src/main/java/nju/seec/helper/service/impl/EventServiceImpl.java

@@ -1,18 +1,15 @@
 package nju.seec.helper.service.impl;
 
-import com.google.common.collect.Lists;
-import nju.seec.helper.sys.dataanalysis.api.EventApi;
-import nju.seec.helper.sys.dataanalysis.model.Event;
 import nju.seec.helper.dao.*;
-import nju.seec.helper.entity.*;
-import nju.seec.helper.enums.UserRole;
 import nju.seec.helper.service.EventService;
+import nju.seec.helper.sys.dataanalysis.api.EventApi;
+import nju.seec.helper.sys.dataanalysis.model.Event;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.time.LocalDateTime;
 import java.util.List;
-import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @author cst
@@ -39,34 +36,20 @@ public class EventServiceImpl implements EventService {
     @Transactional(readOnly = true)
     @Override
     public List<Event> getAllEvents() {
-        List<Event> events = Lists.newArrayList();
-        List<Course> courses = courseDAO.findAll();
-        courses.forEach(course -> {
-            List<Choose> chooses = chooseDAO.findByCourseId(course.getId());
-            chooses.forEach(
-                    choose -> {
-                        User student = userDAO.findUserById(choose.getStudentId());
-                        if (student.getPid() != null) {
-                            events.add(eventApi.createParticipateClassEventDTO(String.valueOf(Objects.requireNonNull(student).getPid()), LocalDateTime.now(), course));
-                        }
-                    }
-            );
-        });
-        List<Comment> comments = commentDAO.findAll();
-        comments.forEach(comment -> {
-            User user = comment.getUser();
-            if (user.getPid() != null && user.getRole() == UserRole.STUDENT) {
-                events.add(eventApi.createInitPostEventDTO(String.valueOf(user.getPid()), comment.getCreateAt(), comment.getTitle()));
-            }
-        });
-        List<QuizStudentAnswer> quizStudentAnswers = quizStudentAnswerDAO.findAll();
-        quizStudentAnswers.forEach(quizStudentAnswer -> {
-            User student = quizStudentAnswer.getStudent();
-            if (student.getPid() != null) {
-                Quiz quiz = quizStudentAnswer.getQuiz();
-                events.add(eventApi.createCompleteQuizEventDTO(String.valueOf(student.getPid()), quizStudentAnswer.getSubmitAt(), quiz));
-            }
-        });
-        return events;
+        return Stream.of(
+                courseDAO.findAll()
+                        .stream()
+                        .flatMap(course -> chooseDAO.findByCourseId(course.getId())
+                                .stream()
+                                .map(choose -> eventApi.createParticipateClassEventDTO(String.valueOf(userDAO.findUserById(choose.getStudentId()).getPid()), choose.getChooseAt(), course)))
+                , commentDAO.findAll()
+                        .stream()
+                        .map(comment -> eventApi.createInitPostEventDTO(String.valueOf(comment.getUser().getPid()), comment.getCreateAt(), comment.getTitle()))
+                , quizStudentAnswerDAO.findAll()
+                        .stream()
+                        .map(quizStudentAnswer -> eventApi.createCompleteQuizEventDTO(String.valueOf(quizStudentAnswer.getStudent().getPid()), quizStudentAnswer.getSubmitAt(), quizStudentAnswer.getQuiz()))
+        )
+                .flatMap(eventStream -> eventStream)
+                .collect(Collectors.toList());
     }
 }

+ 76 - 19
src/main/java/nju/seec/helper/service/impl/QuestionServiceImpl.java

@@ -1,21 +1,11 @@
 package nju.seec.helper.service.impl;
 
 import cn.hutool.core.util.IdUtil;
-import nju.seec.helper.sys.bok.api.QuestionApi;
-import nju.seec.helper.sys.bok.api.QuestionQueryApi;
-import nju.seec.helper.sys.bok.api.RecordApi;
-import nju.seec.helper.sys.bok.api.UserInteractiveApi;
-import nju.seec.helper.sys.bok.dto.CollectDTO;
-import nju.seec.helper.sys.bok.dto.RateDTO;
-import nju.seec.helper.sys.bok.vo.InteractiveVO;
-import nju.seec.helper.sys.bok.vo.QuestionVO;
-import nju.seec.helper.sys.bok.vo.RecordQuestionVO;
+import com.google.common.collect.Lists;
 import nju.seec.helper.aspect.auth.LoginUser;
-import nju.seec.helper.dao.ChooseDAO;
-import nju.seec.helper.dao.QuestionRecordDAO;
-import nju.seec.helper.dao.QuizDAO;
-import nju.seec.helper.dao.UserDAO;
+import nju.seec.helper.dao.*;
 import nju.seec.helper.dto.question.BaseQuestionDTO;
+import nju.seec.helper.dto.question.StudentAnswerDTO;
 import nju.seec.helper.entity.QuestionRecord;
 import nju.seec.helper.entity.Quiz;
 import nju.seec.helper.enums.ExceptionType;
@@ -23,6 +13,19 @@ import nju.seec.helper.enums.QuizState;
 import nju.seec.helper.enums.UserRole;
 import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.service.QuestionService;
+import nju.seec.helper.sys.bok.api.QuestionApi;
+import nju.seec.helper.sys.bok.api.QuestionQueryApi;
+import nju.seec.helper.sys.bok.api.RecordApi;
+import nju.seec.helper.sys.bok.api.UserInteractiveApi;
+import nju.seec.helper.sys.bok.dto.CollectDTO;
+import nju.seec.helper.sys.bok.dto.RateDTO;
+import nju.seec.helper.sys.bok.dto.RecordDTO;
+import nju.seec.helper.sys.bok.vo.InteractiveVO;
+import nju.seec.helper.sys.bok.vo.QuestionVO;
+import nju.seec.helper.sys.bok.vo.RecordQuestionVO;
+import nju.seec.helper.sys.dataanalysis.api.ExerciseApi;
+import nju.seec.helper.sys.dataanalysis.model.ExerciseRecord;
+import nju.seec.helper.sys.ml.api.RecommendationApi;
 import nju.seec.helper.vo.question.BaseQuestionVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
@@ -33,6 +36,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -47,30 +51,39 @@ public class QuestionServiceImpl implements QuestionService {
     private final UserDAO userDAO;
     private final QuestionRecordDAO questionRecordDAO;
     private final ChooseDAO chooseDAO;
+    private final CourseDAO courseDAO;
 
     private final QuestionApi questionApi;
     private final QuestionQueryApi questionQueryApi;
     private final UserInteractiveApi userInteractiveApi;
     private final RecordApi recordApi;
+    private final RecommendationApi recommendationApi;
+    private final ExerciseApi exerciseApi;
 
     @Autowired
     public QuestionServiceImpl(QuizDAO quizDAO
             , UserDAO userDAO
             , QuestionRecordDAO questionRecordDAO
             , ChooseDAO chooseDAO
+            , CourseDAO courseDAO
             , QuestionApi questionApi
             , QuestionQueryApi questionQueryApi
             , UserInteractiveApi userInteractiveApi
             , RecordApi recordApi
+            , RecommendationApi recommendationApi
+            , ExerciseApi exerciseApi
     ) {
         this.quizDAO = quizDAO;
         this.userDAO = userDAO;
         this.questionRecordDAO = questionRecordDAO;
         this.chooseDAO = chooseDAO;
+        this.courseDAO = courseDAO;
         this.questionApi = questionApi;
         this.questionQueryApi = questionQueryApi;
         this.userInteractiveApi = userInteractiveApi;
         this.recordApi = recordApi;
+        this.recommendationApi = recommendationApi;
+        this.exerciseApi = exerciseApi;
     }
 
     @Override
@@ -80,19 +93,16 @@ public class QuestionServiceImpl implements QuestionService {
 
     @Transactional(readOnly = true)
     @Override
-    public List<BaseQuestionVO> getQuestionsByQuiz(LoginUser user, Long quizId) {
+    public List<BaseQuestionVO> getQuestionsByQuizId(LoginUser user, Long quizId) {
         Quiz quiz = quizDAO.findQuizById(quizId);
-        List<String> questionIds = quiz.getQuestions();
-        return questionQueryApi.getQuestionListById(questionIds)
+        return questionQueryApi.getQuestionListById(quiz.getQuestions())
                 .stream()
                 .map(bokQuestion -> BaseQuestionVO.convertBokQuestionToVO(bokQuestion, quiz.getState() == QuizState.CLOSED || user.getRole() == UserRole.TEACHER))
                 .collect(Collectors.toList());
     }
 
-    @Transactional(readOnly = true)
     @Override
-    public List<BaseQuestionVO> getQuestionsByQuiz(Quiz quiz) {
-        List<String> questionIds = quiz.getQuestions();
+    public List<BaseQuestionVO> getQuestionsByIds(List<String> questionIds) {
         return questionQueryApi.getQuestionListById(questionIds)
                 .stream()
                 .map(bokQuestion -> BaseQuestionVO.convertBokQuestionToVO(bokQuestion, true))
@@ -188,6 +198,53 @@ public class QuestionServiceImpl implements QuestionService {
         return userInteractiveApi.getInteractive(String.valueOf(user.getPid()), questionId);
     }
 
+    @Override
+    public List<BaseQuestionVO> getRecommendationQuestions(LoginUser user, List<String> knowledgeId, Integer questionNum) {
+        return questionQueryApi.getQuestionListById(recommendationApi.getRecommendResult(String.valueOf(user.getPid()), knowledgeId, questionNum))
+                .stream()
+                .map(questionVO -> BaseQuestionVO.convertBokQuestionToVO(questionVO, true))
+                .collect(Collectors.toList());
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public List<BaseQuestionVO> getRecommendationQuestionsByCourse(LoginUser user, Long courseId, Integer questionNum) {
+        return this.getRecommendationQuestions(user, courseDAO.findCourseById(courseId).getKnowledgeId(), questionNum);
+    }
+
+    @Override
+    public void record(LoginUser user, StudentAnswerDTO studentAnswerDTO) {
+        if (studentAnswerDTO.getRecords() == null || studentAnswerDTO.getRecords().isEmpty()) {
+            return;
+        }
+        // 获取已有信息
+        String userId = String.valueOf(user.getPid());
+
+        Map<String, StudentAnswerDTO.AnswerRecordDTO> recordMap = studentAnswerDTO.getRecords();
+        List<BaseQuestionVO> baseQuestionVOList = questionQueryApi.getQuestionListById(Lists.newArrayList(recordMap.keySet()))
+                .stream()
+                .map(questionVO -> BaseQuestionVO.convertBokQuestionToVO(questionVO, true))
+                .collect(Collectors.toList());
+
+        // 创建两个列表
+        int size = recordMap.size();
+        List<ExerciseRecord> exerciseRecords = Lists.newArrayListWithExpectedSize(size);
+        List<RecordDTO> records = Lists.newArrayListWithExpectedSize(size);
+
+        // 填充列表
+        baseQuestionVOList.forEach(baseQuestionVO -> {
+            String questionId = baseQuestionVO.getId();
+            StudentAnswerDTO.AnswerRecordDTO answerRecord = recordMap.get(questionId);
+            Object answer = answerRecord.getAnswer();
+            Boolean pass = baseQuestionVO.pass(answer);
+            exerciseRecords.add(exerciseApi.createExerciseRecordDTO(userId, questionId, answerRecord.getStartAt(), answerRecord.getEndAt(), pass));
+            records.add(recordApi.createRecordDTO(userId, questionId, answer, pass, answerRecord.getStartAt(), answerRecord.getEndAt()));
+        });
+
+        exerciseApi.reportExercise(exerciseRecords);
+        recordApi.reportUserRecord(records);
+    }
+
     private static final Set<QuizState> HIDE_QUESTION_ANSWER_STATES = Collections.singleton(QuizState.ONGOING);
 
     private Page<BaseQuestionVO> convert(LoginUser user, Page<QuestionVO> questionVOList) {

+ 1 - 1
src/main/java/nju/seec/helper/service/impl/QuizServiceImpl.java

@@ -108,7 +108,7 @@ public class QuizServiceImpl implements QuizService {
     public QuizVO modifyQuiz(LoginUser user, Long quizId, QuizDTO quizDTO) {
         Quiz quiz = quizDAO.findQuizById(quizId);
 
-        if (existsUpdateAuth(user, quiz)) {
+        if (!existsUpdateAuth(user, quiz)) {
             throw HelperException.of(ExceptionType.FORBIDDEN, "您无权修改该测试");
         }
 

+ 19 - 11
src/main/java/nju/seec/helper/service/impl/QuizStudentAnswerServiceImpl.java

@@ -1,11 +1,12 @@
 package nju.seec.helper.service.impl;
 
+import cn.hutool.core.util.StrUtil;
 import com.google.common.collect.Maps;
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.QuizDAO;
 import nju.seec.helper.dao.QuizStudentAnswerDAO;
 import nju.seec.helper.dao.UserDAO;
-import nju.seec.helper.dto.quiz.QuizStudentAnswerDTO;
+import nju.seec.helper.dto.question.StudentAnswerDTO;
 import nju.seec.helper.entity.Quiz;
 import nju.seec.helper.entity.QuizStudentAnswer;
 import nju.seec.helper.entity.User;
@@ -47,12 +48,12 @@ public class QuizStudentAnswerServiceImpl implements QuizStudentAnswerService {
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public QuizStudentAnswerVO submitQuizStudentAnswer(LoginUser user, Long quizId, QuizStudentAnswerDTO quizStudentAnswerDTO) {
+    public QuizStudentAnswerVO submitQuizStudentAnswer(LoginUser user, Long quizId, StudentAnswerDTO studentAnswerDTO) {
         Quiz quiz = quizDAO.findQuizById(quizId);
 
         switch (quiz.getState()) {
             case NOT_STARTED:
-                throw HelperException.of(ExceptionType.FORBIDDEN, "测试未开始");
+                throw HelperException.of(ExceptionType.FORBIDDEN, "测试未开始,无法提交");
             case CLOSED:
                 throw HelperException.of(ExceptionType.FORBIDDEN, "测试已关闭,无法提交");
             default:
@@ -64,20 +65,27 @@ public class QuizStudentAnswerServiceImpl implements QuizStudentAnswerService {
         QuizStudentAnswer quizStudentAnswer = quizStudentAnswerDAO.findByQuizAndStudent(quiz, student).orElse(new QuizStudentAnswer());
 
         if (quizStudentAnswer.getSubmitNumber() >= quiz.getMaxSubmitNumber()) {
-            throw HelperException.of(ExceptionType.FORBIDDEN, String.format("已达最大提交次数%d次,无法再提交", quiz.getMaxSubmitNumber()));
+            throw HelperException.of(ExceptionType.FORBIDDEN, StrUtil.format("已达最大提交次数{}次,无法提交", quiz.getMaxSubmitNumber()));
         }
 
         // 计算得分
-        List<BaseQuestionVO> questionVOList = questionService.getQuestionsByQuiz(quiz);
+        List<BaseQuestionVO> questionVOList = questionService.getQuestionsByIds(quiz.getQuestions());
 
         AtomicInteger correct = new AtomicInteger();
 
-        Map<String, Object> answers = quizStudentAnswerDTO.getAnswers();
+        Map<String, StudentAnswerDTO.AnswerRecordDTO> records = studentAnswerDTO.getRecords();
         questionVOList.forEach(baseQuestionVO -> {
+            String questionId = baseQuestionVO.getId();
             Object answer;
-            if ((answer = answers.get(baseQuestionVO.getId())) != null && baseQuestionVO.pass(answer)) {
+            if (records.get(questionId) != null) {
+                answer = records.get(questionId).getAnswer();
+            } else {
+                answer = quizStudentAnswer.getAnswers().get(questionId);
+            }
+            if (baseQuestionVO.pass(answer)) {
                 correct.getAndIncrement();
             }
+            quizStudentAnswer.getAnswers().put(questionId, answer);
         });
 
         BigDecimal score = BigDecimal.valueOf(correct.get() * 100).divide(BigDecimal.valueOf(questionVOList.size()), 2, RoundingMode.HALF_UP);
@@ -85,18 +93,18 @@ public class QuizStudentAnswerServiceImpl implements QuizStudentAnswerService {
 
         quizStudentAnswer.setQuiz(quiz)
                 .setStudent(student)
-                .setAnswers(quizStudentAnswerDTO.getAnswers())
                 .setSubmitNumber(quizStudentAnswer.getSubmitNumber() + 1)
-                .setAnswers(answers)
                 .setScore(score);
 
+        questionService.record(user, studentAnswerDTO);
+
         return new QuizStudentAnswerVO(quizStudentAnswerDAO.save(quizStudentAnswer), questionVOList);
     }
 
     @Transactional(readOnly = true)
     @Override
     public Page<QuizStudentAnswerVO> getQuizStudentAnswers(LoginUser user, Long quizId, String key, Pageable pageable) {
-        return quizStudentAnswerDAO.findByQuizIdAndKey(quizId, key, pageable).map(quizStudentAnswer -> new QuizStudentAnswerVO(quizStudentAnswer, questionService.getQuestionsByQuiz(quizDAO.findQuizById(quizId))));
+        return quizStudentAnswerDAO.findByQuizIdAndKey(quizId, key, pageable).map(quizStudentAnswer -> new QuizStudentAnswerVO(quizStudentAnswer, questionService.getQuestionsByIds(quizDAO.findQuizById(quizId).getQuestions())));
     }
 
     @Transactional(readOnly = true)
@@ -104,7 +112,7 @@ public class QuizStudentAnswerServiceImpl implements QuizStudentAnswerService {
     public QuizStudentAnswerVO getQuizStudentAnswer(LoginUser user, Long quizId) {
         return quizStudentAnswerDAO
                 .findByQuizAndStudent(quizDAO.findQuizById(quizId), userDAO.findUserById(user.getId()))
-                .map(quizStudentAnswer -> new QuizStudentAnswerVO(quizStudentAnswer, questionService.getQuestionsByQuiz(quizDAO.findQuizById(quizId))))
+                .map(quizStudentAnswer -> new QuizStudentAnswerVO(quizStudentAnswer, questionService.getQuestionsByIds(quizDAO.findQuizById(quizId).getQuestions())))
                 .orElse(null);
     }
 

+ 2 - 2
src/main/java/nju/seec/helper/service/impl/RecordServiceImpl.java

@@ -1,11 +1,11 @@
 package nju.seec.helper.service.impl;
 
+import nju.seec.helper.dao.UserDAO;
+import nju.seec.helper.service.RecordService;
 import nju.seec.helper.sys.bok.api.RecordApi;
 import nju.seec.helper.sys.bok.vo.RecordQuestionVO;
 import nju.seec.helper.sys.dataanalysis.api.ExerciseApi;
 import nju.seec.helper.sys.dataanalysis.model.ExerciseRecord;
-import nju.seec.helper.dao.UserDAO;
-import nju.seec.helper.service.RecordService;
 import org.springframework.data.domain.PageRequest;
 import org.springframework.stereotype.Service;
 

+ 1 - 1
src/main/java/nju/seec/helper/sys/dataanalysis/api/ExerciseApi.java

@@ -23,7 +23,7 @@ public class ExerciseApi {
 
     @Async
     public void reportExercise(List<ExerciseRecord> exerciseRecords) {
-//        System.out.println(exerciseRecords);
+        System.out.println(exerciseRecords);
 //        restRequestUtil.sendPostRequest(exerciseReportUrl, exerciseRecords, Boolean.class);
     }
 

+ 32 - 0
src/main/java/nju/seec/helper/sys/ml/api/RecommendationApi.java

@@ -0,0 +1,32 @@
+package nju.seec.helper.sys.ml.api;
+
+import com.google.common.collect.ImmutableMap;
+import lombok.Data;
+import nju.seec.helper.util.RestRequestUtil;
+import nju.seec.helper.util.StringUtils;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author cst
+ */
+@Component
+@Data
+@ConfigurationProperties("ml.recommendation")
+public class RecommendationApi {
+    private String getUrl;
+    private final RestRequestUtil restRequestUtil;
+
+    @SuppressWarnings("unchecked")
+    public List<String> getRecommendResult(String userId, List<String> knowledgeId, Integer questionNum) {
+        Map<String, ?> uriVariables = ImmutableMap.<String, Object>builder()
+                .put("userId", userId)
+                .put("knowledgeId", knowledgeId == null ? "" : StringUtils.getParamValuesString(knowledgeId, "knowledgeId"))
+                .put("questionNum", questionNum)
+                .build();
+        return restRequestUtil.sendGetRequest(getUrl, List.class, uriVariables);
+    }
+}

+ 15 - 0
src/main/java/nju/seec/helper/sys/ml/vo/RecommendResultVO.java

@@ -0,0 +1,15 @@
+//package nju.seec.helper.sys.ml.vo;
+//
+//import lombok.Builder;
+//import lombok.Data;
+//
+//import java.util.List;
+//
+///**
+// * @author Kunduin
+// */
+//@Data
+//@Builder
+//public class RecommendResultVO {
+//    List<String> questionIdList;
+//}

+ 11 - 2
src/main/java/nju/seec/helper/util/StringUtils.java

@@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil;
 import lombok.experimental.UtilityClass;
 import org.springframework.data.domain.Sort;
 
+import java.util.Collection;
+
 /**
  * @author cst
  */
@@ -15,8 +17,15 @@ public class StringUtils {
 
     public String getSortString(Sort sort) {
         return sort.stream()
-                .map(order -> StrUtil.concat(true, "sort=", order.getProperty(), ",", order.getDirection().toString()))
-                .reduce((o1, o2) -> StrUtil.join("&", o1, o2))
+                .map(order -> StrUtil.concat(true, order.getProperty(), ",", order.getDirection().toString()))
+                .reduce((o1, o2) -> StrUtil.join("&sort=", o1, o2))
+                .orElse("");
+    }
+
+    public String getParamValuesString(Collection<?> values, String param) {
+        return values.stream()
+                .map(Object::toString)
+                .reduce((o1, o2) -> StrUtil.join(param, o1, o2))
                 .orElse("");
     }
 }

+ 5 - 0
src/main/java/nju/seec/helper/vo/CourseVO.java

@@ -5,6 +5,7 @@ import lombok.NonNull;
 import nju.seec.helper.entity.Course;
 
 import java.time.LocalDateTime;
+import java.util.List;
 
 /**
  * @author cst
@@ -19,6 +20,8 @@ public class CourseVO {
     private Long teacherId;
     private String teacherName;
 
+    private List<String> knowledgeId;
+
     public CourseVO(@NonNull Course course) {
         this.id = course.getId();
         this.name = course.getName();
@@ -27,5 +30,7 @@ public class CourseVO {
 
         this.teacherId = course.getTeacher().getId();
         this.teacherName = course.getTeacher().getName();
+
+        this.knowledgeId = course.getKnowledgeId();
     }
 }

+ 2 - 0
src/main/java/nju/seec/helper/vo/question/BaseQuestionVO.java

@@ -32,6 +32,8 @@ public abstract class BaseQuestionVO {
                 return new ChoiceQuestionVO(questionVO, withAnswer);
             case true_false:
                 return new TrueOrFalseQuestionVO(questionVO, withAnswer);
+//            case multiple_choice:
+//                return new MultipleChoiceQuestionVO(questionVO, withAnswer);
             default:
                 return null;
         }

+ 36 - 0
src/main/java/nju/seec/helper/vo/question/MultipleChoiceQuestionVO.java

@@ -0,0 +1,36 @@
+//package nju.seec.helper.vo.question;
+//
+//import lombok.Data;
+//import lombok.EqualsAndHashCode;
+//import nju.seec.helper.sys.bok.vo.QuestionVO;
+//
+//import java.util.Arrays;
+//
+///**
+// * @author cst
+// */
+//@EqualsAndHashCode(callSuper = true)
+//@Data
+//public class MultipleChoiceQuestionVO extends BaseQuestionVO {
+//    private String[] answer;
+//
+//    public MultipleChoiceQuestionVO(QuestionVO questionVO, boolean withAnswer) {
+//        super(questionVO);
+//
+//        if (withAnswer) {
+//            this.answer = questionVO.getAnswer().split(";");
+//        }
+//    }
+//
+//    @Override
+//    public boolean pass(Object answer) {
+//        if (!(answer instanceof String[])) {
+//            return false;
+//        }
+//
+//        Arrays.sort((String[]) answer);
+//        Arrays.sort(this.answer);
+//
+//        return Arrays.equals((String[]) answer, this.answer);
+//    }
+//}

+ 8 - 3
src/main/resources/application-deploy.yml

@@ -64,7 +64,7 @@ bok:
   knowledge-cache-name: ${helper.sys-name}_bok_knowledge
   question:
     url: ${bok.api-url}/question
-    search-url: ${bok.question.url}?stem={stem}&tag={tag}&knowledgeId={knowledgeId}&page={page}&size={size}&{sort}
+    search-url: ${bok.question.url}?stem={stem}&tag={tag}&knowledgeId={knowledgeId}&page={page}&size={size}&sort={sort}
   question-query:
     url: ${bok.api-url}/question-query
     by-id-url: ${bok.question-query.url}/id?id={id}
@@ -74,11 +74,11 @@ bok:
     url: ${bok.api-url}/user
     rate-url: ${bok.user.url}/{userId}/question/{questionId}/rate
     collection-url: ${bok.user.url}/{userId}/question/{questionId}/collection
-    question-url: ${bok.user.url}/{userId}/question?collection={collection}&rate={rate}&page={page}&size={size}&{sort}
+    question-url: ${bok.user.url}/{userId}/question?collection={collection}&rate={rate}&page={page}&size={size}&sort={sort}
     interactive-url: ${bok.user.url}/{userId}/questionInList?questionId={questionId}
   record:
     url: ${bok.api-url}/record
-    question-url: ${bok.record.url}/{userId}/question?page={page}&size={size}&{sort}
+    question-url: ${bok.record.url}/{userId}/question?page={page}&size={size}&sort={sort}
 data-analysis:
   api-url: http://dataanalysis.seecoder.cn/api
   event:
@@ -87,6 +87,11 @@ data-analysis:
   exercise:
     url: ${data-analysis.api-url}/exercise
     report-url: ${data-analysis.exercise.url}/report
+ml:
+  api-url: http://ml.seec.seecoder.cn/api
+  recommendation:
+    url: ${ml.api-url}/recommendation
+    get-url: ${ml.recommendation.url}?userId={userId}&{knowledgeId}&questionNum={questionNum}
 jwt:
   secret: SEEC-1919810-OIDC-114514
 # 自定义数据

+ 8 - 3
src/main/resources/application-dev.yml

@@ -62,7 +62,7 @@ bok:
   knowledge-cache-name: ${helper.sys-name}_bok_knowledge
   question:
     url: ${bok.api-url}/question
-    search-url: ${bok.question.url}?stem={stem}&tag={tag}&knowledgeId={knowledgeId}&page={page}&size={size}&{sort}
+    search-url: ${bok.question.url}?stem={stem}&tag={tag}&knowledgeId={knowledgeId}&page={page}&size={size}&sort={sort}
   question-query:
     url: ${bok.api-url}/question-query
     by-id-url: ${bok.question-query.url}/id?id={id}
@@ -72,11 +72,11 @@ bok:
     url: ${bok.api-url}/user
     rate-url: ${bok.user.url}/{userId}/question/{questionId}/rate
     collection-url: ${bok.user.url}/{userId}/question/{questionId}/collection
-    question-url: ${bok.user.url}/{userId}/question?collection={collection}&rate={rate}&page={page}&size={size}&{sort}
+    question-url: ${bok.user.url}/{userId}/question?collection={collection}&rate={rate}&page={page}&size={size}&sort={sort}
     interactive-url: ${bok.user.url}/{userId}/questionInList?questionId={questionId}
   record:
     url: ${bok.api-url}/record
-    question-url: ${bok.record.url}/{userId}/question?page={page}&size={size}&{sort}
+    question-url: ${bok.record.url}/{userId}/question?page={page}&size={size}&sort={sort}
 data-analysis:
   api-url: http://dataanalysis.seecoder.cn/api
   event:
@@ -85,6 +85,11 @@ data-analysis:
   exercise:
     url: ${data-analysis.api-url}/exercise
     report-url: ${data-analysis.exercise.url}/report
+ml:
+  api-url: http://ml.seec.seecoder.cn/api
+  recommendation:
+    url: ${ml.api-url}/recommendation
+    get-url: ${ml.recommendation.url}?userId={userId}&{knowledgeId}&questionNum={questionNum}
 jwt:
   secret: SEEC-1919810-OIDC-114514
 # 自定义数据