Procházet zdrojové kódy

feat: 增加向题库提交做题记录功能

ChenSiTong před 6 roky
rodič
revize
d631007b39
27 změnil soubory, kde provedl 402 přidání a 91 odebrání
  1. 4 4
      src/main/java/nju/seec/helper/api/bok/QuestionApi.java
  2. 1 1
      src/main/java/nju/seec/helper/api/bok/QuestionQueryApi.java
  3. 39 0
      src/main/java/nju/seec/helper/api/bok/RecordApi.java
  4. 20 0
      src/main/java/nju/seec/helper/api/bok/dto/RecordDTO.java
  5. 17 0
      src/main/java/nju/seec/helper/api/bok/vo/RecordVO.java
  6. 35 10
      src/main/java/nju/seec/helper/api/dataanalysis/EventApi.java
  7. 16 6
      src/main/java/nju/seec/helper/api/dataanalysis/ExerciseApi.java
  8. 29 0
      src/main/java/nju/seec/helper/api/dataanalysis/constant/EventAction.java
  9. 37 0
      src/main/java/nju/seec/helper/api/dataanalysis/constant/EventType.java
  10. 21 0
      src/main/java/nju/seec/helper/api/dataanalysis/dto/EventDTO.java
  11. 39 0
      src/main/java/nju/seec/helper/api/dataanalysis/dto/ExerciseRecordDTO.java
  12. 8 1
      src/main/java/nju/seec/helper/aspect/auth/AuthAspect.java
  13. 2 2
      src/main/java/nju/seec/helper/aspect/event/Event.java
  14. 23 26
      src/main/java/nju/seec/helper/aspect/event/EventAspect.java
  15. 12 0
      src/main/java/nju/seec/helper/aspect/event/ExerciseRecord.java
  16. 54 0
      src/main/java/nju/seec/helper/aspect/event/ExerciseRecordAspect.java
  17. 4 0
      src/main/java/nju/seec/helper/controller/CommentController.java
  18. 4 0
      src/main/java/nju/seec/helper/controller/CourseController.java
  19. 2 2
      src/main/java/nju/seec/helper/controller/EventController.java
  20. 6 0
      src/main/java/nju/seec/helper/controller/QuizController.java
  21. 2 2
      src/main/java/nju/seec/helper/service/EventService.java
  22. 0 4
      src/main/java/nju/seec/helper/service/impl/CommentServiceImpl.java
  23. 0 4
      src/main/java/nju/seec/helper/service/impl/CourseServiceImpl.java
  24. 7 7
      src/main/java/nju/seec/helper/service/impl/EventServiceImpl.java
  25. 6 6
      src/main/java/nju/seec/helper/service/impl/QuestionServiceImpl.java
  26. 0 6
      src/main/java/nju/seec/helper/service/impl/QuizStudentAnswerServiceImpl.java
  27. 14 10
      src/main/resources/application-dev.yml

+ 4 - 4
src/main/java/nju/seec/helper/api/bok/QuestionApi.java

@@ -46,7 +46,7 @@ public class QuestionApi {
     }
 
     @SuppressWarnings({"unchecked", "rawtypes"})
-    public Page<QuestionVO> searchQuestions(String stem, String tag, String knowledgeId, Pageable pageable) {
+    public Page<QuestionVO> getQuestionList(String stem, String tag, String knowledgeId, Pageable pageable) {
         Map<String, ?> urlParams = (Map) ImmutableMap.builder()
                 .put("stem", stem)
                 .put("tag", tag)
@@ -66,7 +66,7 @@ public class QuestionApi {
         return new PageImpl<>(questionVOList, pageable, result.getPage().getTotalElements());
     }
 
-    public QuestionVO findQuestionsById(String questionId) {
+    public QuestionVO getQuestionsById(String questionId) {
         Object cachedBokQuestion = cacheUtils.get(BOK_QUESTION_CACHE_NAME, questionId);
         if (cachedBokQuestion instanceof QuestionVO) {
             return (QuestionVO) cachedBokQuestion;
@@ -83,11 +83,11 @@ public class QuestionApi {
         return questionVO;
     }
 
-    public QuestionVO modifyQuestion(String questionId, BaseQuestionDTO baseQuestionDTO) {
+    public QuestionVO updateQuestion(String questionId, BaseQuestionDTO baseQuestionDTO) {
         QuestionVO questionVO = createQuestionVO(baseQuestionDTO);
         questionVO.setId(questionId);
         restRequestUtil.sendPutRequest(url + "/" + questionId, questionVO);
-        return findQuestionsById(questionId);
+        return getQuestionsById(questionId);
     }
 
     public void deleteQuestion(String questionId) {

+ 1 - 1
src/main/java/nju/seec/helper/api/bok/QuestionQueryApi.java

@@ -37,7 +37,7 @@ public class QuestionQueryApi {
     }
 
     @SuppressWarnings({"unchecked", "rawtypes"})
-    public List<QuestionVO> queryQuestionById(final List<String> ids) {
+    public List<QuestionVO> getQuestionListById(final List<String> ids) {
         Map<String, QuestionVO> bokQuestionsMap = Maps.newHashMapWithExpectedSize(ids.size());
 
         final Map<String, QuestionVO> cacheBokQuestionsMap = (Map) cacheUtils.multiGet(BOK_QUESTION_CACHE_NAME, ids);

+ 39 - 0
src/main/java/nju/seec/helper/api/bok/RecordApi.java

@@ -0,0 +1,39 @@
+package nju.seec.helper.api.bok;
+
+import lombok.Data;
+import nju.seec.helper.api.bok.dto.RecordDTO;
+import nju.seec.helper.util.RestRequestUtil;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * @author cst
+ */
+@Component
+@ConfigurationProperties("bok.record")
+@Data
+public class RecordApi {
+    private String url;
+    private final RestRequestUtil restRequestUtil;
+
+    @Async
+    public void reportUserRecord(List<RecordDTO> records) {
+        System.out.println(records);
+        restRequestUtil.sendPostRequest(url, records, String.class);
+    }
+
+    public RecordDTO createRecordDTO(String studentId, String questionId, Object answer, Boolean pass, LocalDateTime startAt, LocalDateTime finishAt) {
+        return RecordDTO.builder()
+                .studentId(studentId)
+                .questionId(questionId)
+                .studentAnswer(answer)
+                .pass(pass)
+                .startAt(startAt)
+                .finishAt(finishAt)
+                .build();
+    }
+}

+ 20 - 0
src/main/java/nju/seec/helper/api/bok/dto/RecordDTO.java

@@ -0,0 +1,20 @@
+package nju.seec.helper.api.bok.dto;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author Kunduin
+ */
+@Data
+@Builder
+public class RecordDTO {
+    String questionId;
+    String studentId;
+    Object studentAnswer;
+    Boolean pass;
+    LocalDateTime startAt;
+    LocalDateTime finishAt;
+}

+ 17 - 0
src/main/java/nju/seec/helper/api/bok/vo/RecordVO.java

@@ -0,0 +1,17 @@
+package nju.seec.helper.api.bok.vo;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * @author Kunduin
+ */
+@Data
+@Builder
+public class RecordVO {
+    String id;
+    String userId;
+    String questionId;
+    Object studentAnswer;
+    Boolean pass;
+}

+ 35 - 10
src/main/java/nju/seec/helper/api/dataanalysis/EventApi.java

@@ -1,10 +1,15 @@
 package nju.seec.helper.api.dataanalysis;
 
 import cn.hutool.core.util.StrUtil;
+import lombok.Data;
+import nju.seec.helper.api.dataanalysis.constant.EventAction;
+import nju.seec.helper.api.dataanalysis.constant.EventType;
+import nju.seec.helper.api.dataanalysis.dto.EventDTO;
 import nju.seec.helper.entity.Course;
 import nju.seec.helper.entity.Quiz;
 import nju.seec.helper.util.RestRequestUtil;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 
@@ -14,9 +19,10 @@ import java.time.LocalDateTime;
  * @author cst
  */
 @Component
+@ConfigurationProperties("data-analysis.event")
+@Data
 public class EventApi {
-    @Value("${data-analysis.eventReportUrl}")
-    private String eventReportUrl;
+    private String reportUrl;
     private final RestRequestUtil restRequestUtil;
 
     public EventApi(RestRequestUtil restRequestUtil) {
@@ -24,8 +30,8 @@ public class EventApi {
     }
 
     @Async
-    public void reportEvent(Event event) {
-        System.out.println(event);
+    public void reportEvent(EventDTO eventDTO) {
+        System.out.println(eventDTO);
 //            restRequestUtil.sendPostRequest(eventReportUrl, event, Boolean.class);
     }
 
@@ -37,8 +43,13 @@ public class EventApi {
      * @param course
      * @return
      */
-    public Event createParticipateClassEvent(String userId, LocalDateTime time, Course course) {
-        return new Event().setUserId(userId).setTime(time).setType(EventType.LESSON).setAction(EventAction.PARTICIPATE).setDescription(course.getName());
+    public EventDTO createParticipateClassEventDTO(String userId, LocalDateTime time, Course course) {
+        return EventDTO.builder()
+                .userId(userId).time(time)
+                .type(EventType.LESSON)
+                .action(EventAction.PARTICIPATE)
+                .description(course.getName())
+                .build();
     }
 
     /**
@@ -49,8 +60,15 @@ public class EventApi {
      * @param title
      * @return
      */
-    public Event createInitPostEvent(String userId, LocalDateTime time, String title) {
-        return new Event().setUserId(userId).setTime(time).setType(EventType.POST).setAction(EventAction.INITIATE).setDescription(title);
+    public EventDTO createInitPostEventDTO(String userId, LocalDateTime time, String title) {
+        return EventDTO
+                .builder()
+                .userId(userId)
+                .time(time)
+                .type(EventType.POST)
+                .action(EventAction.INITIATE)
+                .description(title)
+                .build();
     }
 
     /**
@@ -61,7 +79,14 @@ public class EventApi {
      * @param quiz
      * @return
      */
-    public Event createCompleteQuizEvent(String userId, LocalDateTime time, Quiz quiz) {
-        return new Event().setUserId(userId).setTime(time).setType(EventType.QUIZ).setAction(EventAction.COMPLETE).setDescription(StrUtil.join(":", quiz.getCourse().getName(), quiz.getName()));
+    public EventDTO createCompleteQuizEventDTO(String userId, LocalDateTime time, Quiz quiz) {
+        return EventDTO
+                .builder()
+                .userId(userId)
+                .time(time)
+                .type(EventType.QUIZ)
+                .action(EventAction.COMPLETE)
+                .description(StrUtil.join(":", quiz.getCourse().getName(), quiz.getName()))
+                .build();
     }
 }

+ 16 - 6
src/main/java/nju/seec/helper/api/dataanalysis/ExerciseApi.java

@@ -1,7 +1,9 @@
 package nju.seec.helper.api.dataanalysis;
 
+import lombok.Data;
+import nju.seec.helper.api.dataanalysis.dto.ExerciseRecordDTO;
 import nju.seec.helper.util.RestRequestUtil;
-import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 
@@ -13,9 +15,10 @@ import java.util.List;
  * @author cst
  */
 @Component
+@ConfigurationProperties("data-analysis.exercise")
+@Data
 public class ExerciseApi {
-    @Value("${data-analysis.exerciseReportUrl}")
-    private String exerciseReportUrl;
+    private String reportUrl;
     private final RestRequestUtil restRequestUtil;
 
     public ExerciseApi(RestRequestUtil restRequestUtil) {
@@ -23,12 +26,19 @@ public class ExerciseApi {
     }
 
     @Async
-    public void reportExercise(List<ExerciseRecord> exerciseRecords) {
+    public void reportExercise(List<ExerciseRecordDTO> exerciseRecords) {
         System.out.println(exerciseRecords);
 //        restRequestUtil.sendPostRequest(exerciseReportUrl, exerciseRecords, Boolean.class);
     }
 
-    public ExerciseRecord createExerciseRecord(String userId, String questionId, LocalDateTime startAt, LocalDateTime endAt, Boolean pass) {
-        return new ExerciseRecord().setUserId(userId).setQuestionId(questionId).setStartAt(startAt).setEndAt(endAt).setDurationSeconds(Duration.between(startAt, endAt).getSeconds()).setPass(pass);
+    public ExerciseRecordDTO createExerciseRecordDTO(String userId, String questionId, LocalDateTime startAt, LocalDateTime endAt, Boolean pass) {
+        return ExerciseRecordDTO.builder()
+                .userId(userId)
+                .questionId(questionId)
+                .startAt(startAt)
+                .endAt(endAt)
+                .durationSeconds(Duration.between(startAt, endAt).getSeconds())
+                .pass(pass)
+                .build();
     }
 }

+ 29 - 0
src/main/java/nju/seec/helper/api/dataanalysis/constant/EventAction.java

@@ -0,0 +1,29 @@
+package nju.seec.helper.api.dataanalysis.constant;
+
+import lombok.Getter;
+
+/**
+ * @author seec data analysis
+ */
+@Getter
+public enum EventAction {
+    /**
+     * 参与
+     */
+    PARTICIPATE("参与"),
+    /**
+     * 完成
+     */
+    COMPLETE("完成"),
+    /**
+     * 创建
+     */
+    INITIATE("创建"),
+    ;
+
+    private final String description;
+
+    EventAction(String description) {
+        this.description = description;
+    }
+}

+ 37 - 0
src/main/java/nju/seec/helper/api/dataanalysis/constant/EventType.java

@@ -0,0 +1,37 @@
+package nju.seec.helper.api.dataanalysis.constant;
+
+import lombok.Getter;
+
+/**
+ * @author seec data analysis
+ */
+@Getter
+public enum EventType {
+    /**
+     * 课程事件
+     */
+    LESSON("课程"),
+    /**
+     * 帖子事件
+     */
+    POST("帖子"),
+    /**
+     * 测验事件
+     */
+    QUIZ("课堂测验"),
+    /**
+     * 考试事件
+     */
+    EXAM("考试"),
+    /**
+     * 作业事件
+     */
+    HOMEWORK("作业"),
+    ;
+
+    private final String description;
+
+    EventType(String description) {
+        this.description = description;
+    }
+}

+ 21 - 0
src/main/java/nju/seec/helper/api/dataanalysis/dto/EventDTO.java

@@ -0,0 +1,21 @@
+package nju.seec.helper.api.dataanalysis.dto;
+
+import lombok.Builder;
+import lombok.Data;
+import nju.seec.helper.api.dataanalysis.constant.EventAction;
+import nju.seec.helper.api.dataanalysis.constant.EventType;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author seec data analysis
+ */
+@Data
+@Builder
+public class EventDTO {
+    private String userId;
+    private EventType type;
+    private LocalDateTime time;
+    private EventAction action;
+    private String description;
+}

+ 39 - 0
src/main/java/nju/seec/helper/api/dataanalysis/dto/ExerciseRecordDTO.java

@@ -0,0 +1,39 @@
+package nju.seec.helper.api.dataanalysis.dto;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author seec data analysis
+ */
+@Data
+@Builder
+public class ExerciseRecordDTO {
+    /**
+     * 问题的ID
+     */
+    private String questionId;
+    /**
+     * 用户的ID
+     */
+    private String userId;
+    /**
+     * 做题开始时间
+     */
+    private LocalDateTime startAt;
+    /**
+     * 做题结束时间
+     */
+    private LocalDateTime endAt;
+    /**
+     * 做题的时间(秒)
+     */
+    private Long durationSeconds;
+    /**
+     * 该题是否通过
+     */
+    private Boolean pass;
+}

+ 8 - 1
src/main/java/nju/seec/helper/aspect/auth/AuthAspect.java

@@ -16,20 +16,24 @@ import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Before;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
 
 import javax.servlet.http.HttpServletRequest;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Collectors;
 
 /**
  * @author cst
  */
 @Aspect
 @Component
+@Order(1)
 public class AuthAspect {
     @Value("${jwt.secret}")
     private String jwtSecret;
@@ -44,7 +48,10 @@ public class AuthAspect {
     public void authCheck(JoinPoint joinPoint, Auth auth) {
         LoginUser user = getLoginUser();
 
-        if (user.getRole() == null) {
+        if (!Arrays
+                .stream(auth.roles())
+                .collect(Collectors.toSet())
+                .contains(user.getRole())) {
             throw HelperException.of(ExceptionType.FORBIDDEN, String.format("您暂时无法%s,请重新登录", auth.message()));
         }
 

+ 2 - 2
src/main/java/nju/seec/helper/aspect/event/Event.java

@@ -1,7 +1,7 @@
 package nju.seec.helper.aspect.event;
 
-import nju.seec.helper.api.dataanalysis.EventAction;
-import nju.seec.helper.api.dataanalysis.EventType;
+import nju.seec.helper.api.dataanalysis.constant.EventAction;
+import nju.seec.helper.api.dataanalysis.constant.EventType;
 
 import java.lang.annotation.*;
 

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

@@ -2,9 +2,9 @@ package nju.seec.helper.aspect.event;
 
 import com.google.common.collect.ImmutableMap;
 import lombok.SneakyThrows;
-import nju.seec.helper.api.dataanalysis.EventAction;
 import nju.seec.helper.api.dataanalysis.EventApi;
-import nju.seec.helper.api.dataanalysis.EventType;
+import nju.seec.helper.api.dataanalysis.constant.EventAction;
+import nju.seec.helper.api.dataanalysis.constant.EventType;
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.CourseDAO;
 import nju.seec.helper.dao.QuizDAO;
@@ -12,6 +12,7 @@ import nju.seec.helper.dto.comment.CommentDTO;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.annotation.AfterReturning;
 import org.aspectj.lang.annotation.Aspect;
+import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -25,53 +26,48 @@ import java.util.Objects;
  */
 @Aspect
 @Component
+@Order(3)
 public class EventAspect {
     private final Map<EventType, Map<EventAction, EventHandler>> eventHandleMap;
 
     public EventAspect(CourseDAO courseDAO, QuizDAO quizDAO, EventApi eventApi) {
         this.eventHandleMap = ImmutableMap.of(
                 EventType.LESSON, ImmutableMap.of(
-                        EventAction.PARTICIPATE, (joinPoint -> {
+                        EventAction.PARTICIPATE, ((joinPoint, user) -> {
                             Object[] args = joinPoint.getArgs();
-                            LoginUser user = null;
                             Long courseId = null;
                             for (Object arg : args) {
-                                if (arg instanceof LoginUser) {
-                                    user = (LoginUser) arg;
-                                } else if (arg instanceof Long) {
+                                if (arg instanceof Long) {
                                     courseId = (Long) arg;
+                                    break;
                                 }
                             }
-                            eventApi.reportEvent(eventApi.createParticipateClassEvent(String.valueOf(Objects.requireNonNull(user).getPid()), LocalDateTime.now(), courseDAO.findCourseById(courseId)));
+                            eventApi.reportEvent(eventApi.createParticipateClassEventDTO(String.valueOf(user.getPid()), LocalDateTime.now(), courseDAO.findCourseById(courseId)));
                         })),
                 EventType.POST, ImmutableMap.of(
-                        EventAction.INITIATE, (joinPoint -> {
+                        EventAction.INITIATE, ((joinPoint, user) -> {
                             Object[] args = joinPoint.getArgs();
-                            LoginUser user = null;
                             CommentDTO commentDTO = null;
                             for (Object arg : args) {
-                                if (arg instanceof LoginUser) {
-                                    user = (LoginUser) arg;
-                                } else if (arg instanceof CommentDTO) {
+                                if (arg instanceof CommentDTO) {
                                     commentDTO = (CommentDTO) arg;
+                                    break;
                                 }
                             }
-                            eventApi.reportEvent(eventApi.createInitPostEvent(String.valueOf(Objects.requireNonNull(user).getPid()), LocalDateTime.now(), Objects.requireNonNull(commentDTO).getTitle()));
+                            eventApi.reportEvent(eventApi.createInitPostEventDTO(String.valueOf(user.getPid()), LocalDateTime.now(), Objects.requireNonNull(commentDTO).getTitle()));
                         })
                 ),
                 EventType.QUIZ, ImmutableMap.of(
-                        EventAction.COMPLETE, (joinPoint -> {
+                        EventAction.COMPLETE, ((joinPoint, user) -> {
                             Object[] args = joinPoint.getArgs();
-                            LoginUser user = null;
                             Long quizId = null;
                             for (Object arg : args) {
-                                if (arg instanceof LoginUser) {
-                                    user = (LoginUser) arg;
-                                } else if (arg instanceof Long) {
+                                if (arg instanceof Long) {
                                     quizId = (Long) arg;
+                                    break;
                                 }
                             }
-                            eventApi.reportEvent(eventApi.createCompleteQuizEvent(String.valueOf(Objects.requireNonNull(user).getPid()), LocalDateTime.now(), quizDAO.findQuizById(quizId)));
+                            eventApi.reportEvent(eventApi.createCompleteQuizEventDTO(String.valueOf(user.getPid()), LocalDateTime.now(), quizDAO.findQuizById(quizId)));
                         })
                 )
         );
@@ -82,20 +78,21 @@ public class EventAspect {
         /**
          * 处理事件
          *
-         * @param joinPoint
+         * @param joinPoint 插入点
+         * @param user      登录用户
          */
         @Transactional(readOnly = true)
-        void handleEvent(JoinPoint joinPoint);
+        void handleEvent(JoinPoint joinPoint, LoginUser user);
     }
 
-    private final EventHandler doNothing = (joinPoint) -> {
+    private final EventHandler doNothing = (joinPoint, user) -> {
     };
 
     @SneakyThrows
-    @AfterReturning("@annotation(event)")
-    public void reportEvent(JoinPoint joinPoint, Event event) {
+    @AfterReturning("@annotation(event) && args(user,..)")
+    public void reportEvent(JoinPoint joinPoint, Event event, LoginUser user) {
         eventHandleMap.getOrDefault(event.type(), Collections.emptyMap())
                 .getOrDefault(event.action(), doNothing)
-                .handleEvent(joinPoint);
+                .handleEvent(joinPoint, user);
     }
 }

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

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

+ 54 - 0
src/main/java/nju/seec/helper/aspect/event/ExerciseRecordAspect.java

@@ -0,0 +1,54 @@
+package nju.seec.helper.aspect.event;
+
+import com.google.common.collect.Lists;
+import nju.seec.helper.api.bok.RecordApi;
+import nju.seec.helper.api.bok.dto.RecordDTO;
+import nju.seec.helper.api.dataanalysis.ExerciseApi;
+import nju.seec.helper.api.dataanalysis.dto.ExerciseRecordDTO;
+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.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author cst
+ */
+@Aspect
+@Component
+@Order(2)
+public class ExerciseRecordAspect {
+    private final ExerciseApi exerciseApi;
+    private final RecordApi recordApi;
+
+    public ExerciseRecordAspect(ExerciseApi exerciseApi, RecordApi recordApi) {
+        this.exerciseApi = exerciseApi;
+        this.recordApi = recordApi;
+    }
+
+    @AfterReturning(value = "@annotation(exerciseRecord) && args(user,..,quizStudentAnswerDTO)", returning = "quizStudentAnswerVO", argNames = "exerciseRecord,user,quizStudentAnswerDTO,quizStudentAnswerVO")
+    public void reportExerciseRecordEvent(ExerciseRecord exerciseRecord, LoginUser user, QuizStudentAnswerDTO quizStudentAnswerDTO, QuizStudentAnswerVO quizStudentAnswerVO) {
+        String userId = String.valueOf(user.getPid());
+        Map<String, QuizStudentAnswerVO.QuestionStudentAnswerInfo> questionStudentAnswerInfoMap = quizStudentAnswerVO.getAnswers();
+        int size = quizStudentAnswerDTO.getRecords().size();
+        List<ExerciseRecordDTO> exerciseRecords = Lists.newArrayListWithExpectedSize(size);
+        List<RecordDTO> records = Lists.newArrayListWithExpectedSize(size);
+
+        quizStudentAnswerDTO
+                .getRecords()
+                .forEach((key, value) -> {
+                    QuizStudentAnswerVO.QuestionStudentAnswerInfo studentAnswerInfo = questionStudentAnswerInfoMap.get(key);
+                    Boolean pass = studentAnswerInfo.getCorrect();
+                    Object answer = studentAnswerInfo.getAnswer();
+                    exerciseRecords.add(exerciseApi.createExerciseRecordDTO(userId, key, value.getStartAt(), value.getEndAt(), pass));
+                    records.add(recordApi.createRecordDTO(userId, key, answer, pass, value.getStartAt(), value.getEndAt()));
+                });
+        exerciseApi.reportExercise(exerciseRecords);
+        recordApi.reportUserRecord(records);
+    }
+}

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

@@ -1,7 +1,10 @@
 package nju.seec.helper.controller;
 
+import nju.seec.helper.api.dataanalysis.constant.EventAction;
+import nju.seec.helper.api.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.comment.CommentDTO;
 import nju.seec.helper.enums.UserRole;
@@ -33,6 +36,7 @@ public class CommentController {
     /**
      * 发表评论
      */
+    @Event(type = EventType.POST, action = EventAction.INITIATE)
     @Auth(roles = {UserRole.TEACHER, UserRole.STUDENT}, message = "发表评论")
     @PostMapping
     public CommentVO createComment(LoginUser user,

+ 4 - 0
src/main/java/nju/seec/helper/controller/CourseController.java

@@ -1,7 +1,10 @@
 package nju.seec.helper.controller;
 
+import nju.seec.helper.api.dataanalysis.constant.EventAction;
+import nju.seec.helper.api.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;
@@ -64,6 +67,7 @@ public class CourseController {
     /**
      * 学生选课
      */
+    @Event(type = EventType.LESSON, action = EventAction.PARTICIPATE)
     @Auth(roles = UserRole.STUDENT, message = "选课")
     @PostMapping("/{courseId}/choose")
     public void chooseCourse(LoginUser user,

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

@@ -1,6 +1,6 @@
 package nju.seec.helper.controller;
 
-import nju.seec.helper.api.dataanalysis.Event;
+import nju.seec.helper.api.dataanalysis.dto.EventDTO;
 import nju.seec.helper.service.EventService;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -26,7 +26,7 @@ public class EventController {
      * 获取所有事件
      */
     @GetMapping
-    public List<Event> getAllEvents() {
+    public List<EventDTO> getAllEvents() {
         return eventService.getAllEvents();
     }
 }

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

@@ -1,7 +1,11 @@
 package nju.seec.helper.controller;
 
+import nju.seec.helper.api.dataanalysis.constant.EventAction;
+import nju.seec.helper.api.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.event.ExerciseRecord;
 import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.groups.Create;
 import nju.seec.helper.dto.groups.Modify;
@@ -115,6 +119,8 @@ public class QuizController {
     /**
      * 提交作答
      */
+    @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) {

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

@@ -1,6 +1,6 @@
 package nju.seec.helper.service;
 
-import nju.seec.helper.api.dataanalysis.Event;
+import nju.seec.helper.api.dataanalysis.dto.EventDTO;
 
 import java.util.List;
 
@@ -13,5 +13,5 @@ public interface EventService {
      *
      * @return
      */
-    List<Event> getAllEvents();
+    List<EventDTO> getAllEvents();
 }

+ 0 - 4
src/main/java/nju/seec/helper/service/impl/CommentServiceImpl.java

@@ -1,9 +1,6 @@
 package nju.seec.helper.service.impl;
 
-import nju.seec.helper.api.dataanalysis.EventAction;
-import nju.seec.helper.api.dataanalysis.EventType;
 import nju.seec.helper.aspect.auth.LoginUser;
-import nju.seec.helper.aspect.event.Event;
 import nju.seec.helper.dao.CommentDAO;
 import nju.seec.helper.dao.SlideDAO;
 import nju.seec.helper.dao.UserDAO;
@@ -42,7 +39,6 @@ public class CommentServiceImpl implements CommentService {
         this.messageService = messageService;
     }
 
-    @Event(type = EventType.POST, action = EventAction.INITIATE)
     @Transactional(rollbackFor = Exception.class)
     @Override
     public CommentVO createComment(LoginUser user, CommentDTO commentDTO) {

+ 0 - 4
src/main/java/nju/seec/helper/service/impl/CourseServiceImpl.java

@@ -1,9 +1,6 @@
 package nju.seec.helper.service.impl;
 
-import nju.seec.helper.api.dataanalysis.EventAction;
-import nju.seec.helper.api.dataanalysis.EventType;
 import nju.seec.helper.aspect.auth.LoginUser;
-import nju.seec.helper.aspect.event.Event;
 import nju.seec.helper.dao.ChooseDAO;
 import nju.seec.helper.dao.CourseDAO;
 import nju.seec.helper.dao.UserDAO;
@@ -90,7 +87,6 @@ public class CourseServiceImpl implements CourseService {
         courseDAO.save(course);
     }
 
-    @Event(type = EventType.LESSON, action = EventAction.PARTICIPATE)
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void chooseCourse(LoginUser student, Long courseId, ChooseDTO chooseDTO) {

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

@@ -1,7 +1,7 @@
 package nju.seec.helper.service.impl;
 
 import com.google.common.collect.Lists;
-import nju.seec.helper.api.dataanalysis.Event;
+import nju.seec.helper.api.dataanalysis.dto.EventDTO;
 import nju.seec.helper.api.dataanalysis.EventApi;
 import nju.seec.helper.dao.*;
 import nju.seec.helper.entity.*;
@@ -38,8 +38,8 @@ public class EventServiceImpl implements EventService {
 
     @Transactional(readOnly = true)
     @Override
-    public List<Event> getAllEvents() {
-        List<Event> events = Lists.newArrayList();
+    public List<EventDTO> getAllEvents() {
+        List<EventDTO> eventDTOS = Lists.newArrayList();
         List<Course> courses = courseDAO.findAll();
         courses.forEach(course -> {
             List<Choose> chooses = chooseDAO.findByCourseId(course.getId());
@@ -47,7 +47,7 @@ public class EventServiceImpl implements EventService {
                     choose -> {
                         User student = userDAO.findUserById(choose.getStudentId());
                         if (student.getPid() != null) {
-                            events.add(eventApi.createParticipateClassEvent(String.valueOf(Objects.requireNonNull(student).getPid()), LocalDateTime.now(), course));
+                            eventDTOS.add(eventApi.createParticipateClassEventDTO(String.valueOf(Objects.requireNonNull(student).getPid()), LocalDateTime.now(), course));
                         }
                     }
             );
@@ -56,7 +56,7 @@ public class EventServiceImpl implements EventService {
         comments.forEach(comment -> {
             User user = comment.getUser();
             if (user.getPid() != null && user.getRole() == UserRole.STUDENT) {
-                events.add(eventApi.createInitPostEvent(String.valueOf(user.getPid()), comment.getCreateAt(), comment.getTitle()));
+                eventDTOS.add(eventApi.createInitPostEventDTO(String.valueOf(user.getPid()), comment.getCreateAt(), comment.getTitle()));
             }
         });
         List<QuizStudentAnswer> quizStudentAnswers = quizStudentAnswerDAO.findAll();
@@ -64,9 +64,9 @@ public class EventServiceImpl implements EventService {
             User student = quizStudentAnswer.getStudent();
             if (student.getPid() != null) {
                 Quiz quiz = quizStudentAnswer.getQuiz();
-                events.add(eventApi.createCompleteQuizEvent(String.valueOf(student.getPid()), quizStudentAnswer.getSubmitAt(), quiz));
+                eventDTOS.add(eventApi.createCompleteQuizEventDTO(String.valueOf(student.getPid()), quizStudentAnswer.getSubmitAt(), quiz));
             }
         });
-        return events;
+        return eventDTOS;
     }
 }

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

@@ -62,7 +62,7 @@ public class QuestionServiceImpl implements QuestionService {
 
     @Override
     public Page<BaseQuestionVO> getQuestions(LoginUser user, String stem, String tag, String knowledgeId, Pageable pageable) {
-        return questionApi.searchQuestions(stem, tag, knowledgeId, pageable).map(bokQuestion -> BaseQuestionVO.convertBokQuestionToVO(bokQuestion, true));
+        return questionApi.getQuestionList(stem, tag, knowledgeId, pageable).map(bokQuestion -> BaseQuestionVO.convertBokQuestionToVO(bokQuestion, true));
     }
 
     @Transactional(readOnly = true)
@@ -70,7 +70,7 @@ public class QuestionServiceImpl implements QuestionService {
     public List<BaseQuestionVO> getQuestionsByQuiz(LoginUser user, Long quizId) {
         Quiz quiz = quizDAO.findQuizById(quizId);
         List<String> questionIds = quiz.getQuestions();
-        return questionQueryApi.queryQuestionById(questionIds)
+        return questionQueryApi.getQuestionListById(questionIds)
                 .stream()
                 .map(bokQuestion -> BaseQuestionVO.convertBokQuestionToVO(bokQuestion, quiz.getState() == QuizState.CLOSED || user.getRole() == UserRole.TEACHER))
                 .collect(Collectors.toList());
@@ -80,7 +80,7 @@ public class QuestionServiceImpl implements QuestionService {
     @Override
     public List<BaseQuestionVO> getQuestionsByQuiz(Quiz quiz) {
         List<String> questionIds = quiz.getQuestions();
-        return questionQueryApi.queryQuestionById(questionIds)
+        return questionQueryApi.getQuestionListById(questionIds)
                 .stream()
                 .map(bokQuestion -> BaseQuestionVO.convertBokQuestionToVO(bokQuestion, true))
                 .collect(Collectors.toList());
@@ -88,7 +88,7 @@ public class QuestionServiceImpl implements QuestionService {
 
     @Override
     public BaseQuestionVO getOneQuestion(LoginUser user, String questionId) {
-        return BaseQuestionVO.convertBokQuestionToVO(questionApi.findQuestionsById(questionId), true);
+        return BaseQuestionVO.convertBokQuestionToVO(questionApi.getQuestionsById(questionId), true);
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -112,7 +112,7 @@ public class QuestionServiceImpl implements QuestionService {
     public BaseQuestionVO modifyQuestion(LoginUser user, String questionId, BaseQuestionDTO baseQuestionDTO) {
         QuestionRecord questionRecord = questionRecordDAO.findByTeacherAndQuestionId(userDAO.findUserById(user.getId()), questionId)
                 .orElseThrow(() -> HelperException.of(ExceptionType.FORBIDDEN, "您无权修改该问题"));
-        QuestionVO questionVO = questionApi.modifyQuestion(questionId, baseQuestionDTO);
+        QuestionVO questionVO = questionApi.updateQuestion(questionId, baseQuestionDTO);
         questionRecordDAO.save(questionRecord);
         return BaseQuestionVO.convertBokQuestionToVO(questionVO, true);
     }
@@ -132,7 +132,7 @@ public class QuestionServiceImpl implements QuestionService {
         Page<String> questionIdPage = questionRecordDAO.findQuestionIdsByTeacher(userDAO.findUserById(user.getId()), pageable);
 
         return new PageImpl<>(
-                questionQueryApi.queryQuestionById(questionIdPage.getContent())
+                questionQueryApi.getQuestionListById(questionIdPage.getContent())
                         .stream()
                         .map(bokQuestion -> BaseQuestionVO.convertBokQuestionToVO(bokQuestion, true))
                         .collect(Collectors.toList())

+ 0 - 6
src/main/java/nju/seec/helper/service/impl/QuizStudentAnswerServiceImpl.java

@@ -1,11 +1,7 @@
 package nju.seec.helper.service.impl;
 
 import com.google.common.collect.Maps;
-import nju.seec.helper.api.dataanalysis.EventAction;
-import nju.seec.helper.api.dataanalysis.EventType;
 import nju.seec.helper.aspect.auth.LoginUser;
-import nju.seec.helper.aspect.event.Event;
-import nju.seec.helper.aspect.event.ExerciseRecordEvent;
 import nju.seec.helper.dao.QuizDAO;
 import nju.seec.helper.dao.QuizStudentAnswerDAO;
 import nju.seec.helper.dao.UserDAO;
@@ -49,8 +45,6 @@ public class QuizStudentAnswerServiceImpl implements QuizStudentAnswerService {
         this.questionService = questionService;
     }
 
-    @ExerciseRecordEvent
-    @Event(type = EventType.QUIZ, action = EventAction.COMPLETE)
     @Transactional(rollbackFor = Exception.class)
     @Override
     public QuizStudentAnswerVO submitQuizStudentAnswer(LoginUser user, Long quizId, QuizStudentAnswerDTO quizStudentAnswerDTO) {

+ 14 - 10
src/main/resources/application-dev.yml

@@ -55,20 +55,22 @@ aliyun:
 #    signName: seeccoder
 #    templateCode: SMS_181555598
 bok:
-  url: http://120.78.159.171:6060
+  api-url: http://120.78.159.171:6060/api
   question:
-    url: ${bok.url}/api/question
+    url: ${bok.api-url}/question
     search-url: ${bok.question.url}?stem={stem}&tag={tag}&knowledgeId={knowledgeId}&page={page}&size={size}&{sort}
   question-query:
-    url: ${bok.url}/api/question-query
+    url: ${bok.api-url}/question-query
     by-id-url: ${bok.question-query.url}/id?id={id}
   knowledge:
-    url: ${bok.url}/api/knowledge
+    url: ${bok.api-url}/knowledge
   user:
-    url: ${bok.url}/api/user
+    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}
+  record:
+    url: ${bok.api-url}/record
 #  tqUrl: ${bok.url}/api/question
 #  tqSearchUrl: ${bok.tqUrl}
 #  tqStemSearchUrl: ${bok.tqSearchUrl}/findByStemLike?content={content}&size={size}&page={page}
@@ -76,11 +78,13 @@ bok:
 #  knowledgeUrl: ${bok.url}/api/neo
 #  knowledgeGetAllUrl: ${bok.knowledgeUrl}/getAll
 data-analysis:
-  url: http://dataanalysis.seecoder.cn
-  eventUrl: ${data-analysis.url}/api/event
-  eventReportUrl: ${data-analysis.eventUrl}/report
-  exerciseUrl: ${data-analysis.url}/api/exercise
-  exerciseReportUrl: ${data-analysis.exerciseUrl}/report
+  api-url: http://dataanalysis.seecoder.cn/api
+  event:
+    url: ${data-analysis.api-url}/event
+    report-url: ${data-analysis.event.url}/report
+  exercise:
+    url: ${data-analysis.api-url}/exercise
+    report-url: ${data-analysis.exercise.url}/report
 jwt:
   secret: SEEC-1919810-OIDC-114514
 # 自定义数据