瀏覽代碼

feat: 增加事件通知切面

ChenSiTong 6 年之前
父節點
當前提交
e3f4450b30
共有 32 個文件被更改,包括 398 次插入175 次删除
  1. 1 1
      pom.xml
  2. 38 39
      src/main/java/nju/seec/helper/api/bok/QuestionApi.java
  3. 30 0
      src/main/java/nju/seec/helper/api/bok/QuestionVO.java
  4. 5 4
      src/main/java/nju/seec/helper/api/dataanalysis/Event.java
  5. 9 0
      src/main/java/nju/seec/helper/api/dataanalysis/EventAction.java
  6. 71 25
      src/main/java/nju/seec/helper/api/dataanalysis/EventApi.java
  7. 3 4
      src/main/java/nju/seec/helper/aspect/auth/AuthAspect.java
  8. 1 0
      src/main/java/nju/seec/helper/aspect/auth/LoginUser.java
  9. 18 0
      src/main/java/nju/seec/helper/aspect/event/Event.java
  10. 110 0
      src/main/java/nju/seec/helper/aspect/event/EventAspect.java
  11. 2 2
      src/main/java/nju/seec/helper/controller/EventController.java
  12. 0 3
      src/main/java/nju/seec/helper/controller/UserController.java
  13. 0 5
      src/main/java/nju/seec/helper/entity/User.java
  14. 12 1
      src/main/java/nju/seec/helper/enums/QuizState.java
  15. 20 1
      src/main/java/nju/seec/helper/enums/SlideState.java
  16. 2 2
      src/main/java/nju/seec/helper/service/EventService.java
  17. 4 0
      src/main/java/nju/seec/helper/service/impl/CommentServiceImpl.java
  18. 2 1
      src/main/java/nju/seec/helper/service/impl/CourseFileServiceImpl.java
  19. 4 0
      src/main/java/nju/seec/helper/service/impl/CourseServiceImpl.java
  20. 14 30
      src/main/java/nju/seec/helper/service/impl/EventServiceImpl.java
  21. 6 6
      src/main/java/nju/seec/helper/service/impl/QuestionServiceImpl.java
  22. 1 1
      src/main/java/nju/seec/helper/service/impl/QuizServiceImpl.java
  23. 4 0
      src/main/java/nju/seec/helper/service/impl/QuizStudentAnswerServiceImpl.java
  24. 2 11
      src/main/java/nju/seec/helper/service/impl/SlideServiceImpl.java
  25. 0 1
      src/main/java/nju/seec/helper/service/impl/UserServiceImpl.java
  26. 0 4
      src/main/java/nju/seec/helper/vo/UserVO.java
  27. 12 11
      src/main/java/nju/seec/helper/vo/question/BaseQuestionVO.java
  28. 6 5
      src/main/java/nju/seec/helper/vo/question/ChoiceQuestionVO.java
  29. 5 4
      src/main/java/nju/seec/helper/vo/question/TrueOrFalseQuestionVO.java
  30. 2 0
      src/main/resources/application-deploy.yml
  31. 5 5
      src/main/resources/application-dev.yml
  32. 9 9
      src/main/resources/hibernate.properties

+ 1 - 1
pom.xml

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

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

@@ -14,7 +14,6 @@ import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.util.Consts;
 import nju.seec.helper.util.RestRequestUtil;
 import nju.seec.helper.util.cache.GuavaCacheUtils;
-import nju.seec.helper.vo.question.BokQuestionVO;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageImpl;
@@ -48,7 +47,7 @@ public class QuestionApi {
         this.cacheUtils = cacheUtils;
     }
 
-    public Page<BokQuestionVO> bokFindByStemLike(String stem, Pageable pageable) {
+    public Page<QuestionVO> bokFindByStemLike(String stem, Pageable pageable) {
         Map<String, String> urlParams = ImmutableMap.of(
                 "content", stem,
                 "page", String.valueOf(1 + pageable.getPageNumber()),
@@ -56,18 +55,18 @@ public class QuestionApi {
         );
         BokSearchResult result = restRequestUtil.sendGetRequest(tqStemSearchUrl, BokSearchResult.class, urlParams);
 
-        List<BokQuestionVO> bokQuestionVOList = result.getEmbedded().getQuestions();
-        cacheUtils.setAll(BOK_QUESTION_CACHE_NAME, bokQuestionVOList.parallelStream().collect(Collectors.toMap(BokQuestionVO::getId, bokQuestion -> bokQuestion)));
-        return new PageImpl<>(bokQuestionVOList, pageable, result.getPage().getTotalElements());
+        List<QuestionVO> questionVOList = result.getEmbedded().getQuestions();
+        cacheUtils.setAll(BOK_QUESTION_CACHE_NAME, questionVOList.parallelStream().collect(Collectors.toMap(QuestionVO::getId, bokQuestion -> bokQuestion)));
+        return new PageImpl<>(questionVOList, pageable, result.getPage().getTotalElements());
     }
 
     private static final String BOK_QUESTION_CACHE_NAME = Consts.SYS_NAME + "_bok_question";
 
     @SuppressWarnings({"unchecked", "rawtypes"})
-    public List<BokQuestionVO> bokFindByIdIn(final List<String> ids) {
-        Map<String, BokQuestionVO> bokQuestionsMap = Maps.newHashMapWithExpectedSize(ids.size());
+    public List<QuestionVO> bokFindByIdIn(final List<String> ids) {
+        Map<String, QuestionVO> bokQuestionsMap = Maps.newHashMapWithExpectedSize(ids.size());
 
-        final Map<String, BokQuestionVO> cacheBokQuestionsMap = (Map) cacheUtils.multiGet(BOK_QUESTION_CACHE_NAME, ids);
+        final Map<String, QuestionVO> cacheBokQuestionsMap = (Map) cacheUtils.multiGet(BOK_QUESTION_CACHE_NAME, ids);
         bokQuestionsMap.putAll(cacheBokQuestionsMap);
 
         List<String> requestIds = Lists.newArrayList(ids);
@@ -77,9 +76,9 @@ public class QuestionApi {
             Map<String, String> urlParams = ImmutableMap.of("id", requestIds.stream().reduce((a, b) -> a + "," + b).orElse(""));
 
             BokSearchResult result = restRequestUtil.sendGetRequest(tqIdSearchUrl, BokSearchResult.class, urlParams);
-            List<BokQuestionVO> bokQuestionVOList = result.getEmbedded().getQuestions();
+            List<QuestionVO> questionVOList = result.getEmbedded().getQuestions();
 
-            Map<String, BokQuestionVO> remoteBokQuestionsMap = bokQuestionVOList.parallelStream().collect(Collectors.toMap(BokQuestionVO::getId, bokQuestion -> bokQuestion));
+            Map<String, QuestionVO> remoteBokQuestionsMap = questionVOList.parallelStream().collect(Collectors.toMap(QuestionVO::getId, bokQuestion -> bokQuestion));
 
             bokQuestionsMap.putAll(remoteBokQuestionsMap);
             cacheUtils.setAll(BOK_QUESTION_CACHE_NAME, (Map) remoteBokQuestionsMap);
@@ -90,27 +89,27 @@ public class QuestionApi {
                 .collect(Collectors.toList());
     }
 
-    public BokQuestionVO bokFindById(String questionId) {
+    public QuestionVO bokFindById(String questionId) {
         Object cachedBokQuestion = cacheUtils.get(BOK_QUESTION_CACHE_NAME, questionId);
-        if (cachedBokQuestion instanceof BokQuestionVO) {
-            return (BokQuestionVO) cachedBokQuestion;
+        if (cachedBokQuestion instanceof QuestionVO) {
+            return (QuestionVO) cachedBokQuestion;
         }
-        BokQuestionVO bokQuestionVO = restRequestUtil.sendGetRequest(tqUrl + "/" + questionId, BokQuestionVO.class, Collections.emptyMap());
-        cacheUtils.set(BOK_QUESTION_CACHE_NAME, bokQuestionVO.getId(), bokQuestionVO);
-        return bokQuestionVO;
+        QuestionVO questionVO = restRequestUtil.sendGetRequest(tqUrl + "/" + questionId, QuestionVO.class, Collections.emptyMap());
+        cacheUtils.set(BOK_QUESTION_CACHE_NAME, questionVO.getId(), questionVO);
+        return questionVO;
     }
 
-    public BokQuestionVO createQuestion(String questionId, BaseQuestionDTO baseQuestionDTO) {
-        BokQuestionVO bokQuestionVO = getQuestion(baseQuestionDTO);
-        bokQuestionVO.setId(questionId);
-        bokQuestionVO = restRequestUtil.sendPostRequest(tqUrl, bokQuestionVO, BokQuestionVO.class);
-        return bokQuestionVO;
+    public QuestionVO createQuestion(String questionId, BaseQuestionDTO baseQuestionDTO) {
+        QuestionVO questionVO = getQuestion(baseQuestionDTO);
+        questionVO.setId(questionId);
+        questionVO = restRequestUtil.sendPostRequest(tqUrl, questionVO, QuestionVO.class);
+        return questionVO;
     }
 
-    public BokQuestionVO modifyQuestion(String questionId, BaseQuestionDTO baseQuestionDTO) {
-        BokQuestionVO bokQuestionVO = getQuestion(baseQuestionDTO);
-        bokQuestionVO.setId(questionId);
-        restRequestUtil.sendPutRequest(tqUrl + "/" + questionId, bokQuestionVO);
+    public QuestionVO modifyQuestion(String questionId, BaseQuestionDTO baseQuestionDTO) {
+        QuestionVO questionVO = getQuestion(baseQuestionDTO);
+        questionVO.setId(questionId);
+        restRequestUtil.sendPutRequest(tqUrl + "/" + questionId, questionVO);
         return bokFindById(questionId);
     }
 
@@ -122,31 +121,31 @@ public class QuestionApi {
 
     }
 
-    private BokQuestionVO getQuestion(BaseQuestionDTO baseQuestionDTO) {
-        BokQuestionVO bokQuestionVO = new BokQuestionVO();
-        bokQuestionVO.setType(baseQuestionDTO.getType());
-        bokQuestionVO.setStem(baseQuestionDTO.getStem());
-        bokQuestionVO.setKeyPoints(baseQuestionDTO.getKeyPoints());
-        bokQuestionVO.setTags(baseQuestionDTO.getTags());
-        bokQuestionVO.setKnowledgeId(baseQuestionDTO.getKnowledgeId());
+    private QuestionVO getQuestion(BaseQuestionDTO baseQuestionDTO) {
+        QuestionVO questionVO = new QuestionVO();
+        questionVO.setType(baseQuestionDTO.getType());
+        questionVO.setStem(baseQuestionDTO.getStem());
+        questionVO.setKeyPoints(baseQuestionDTO.getKeyPoints());
+        questionVO.setTags(baseQuestionDTO.getTags());
+        questionVO.setKnowledgeId(baseQuestionDTO.getKnowledgeId());
 
         final QuestionType questionType = QuestionType.getQuestionType(baseQuestionDTO.getType());
         switch (Objects.requireNonNull(questionType)) {
             case CHOICE:
                 ChoiceQuestionDTO choiceQuestionDTO = (ChoiceQuestionDTO) baseQuestionDTO;
-                bokQuestionVO.setOptions(choiceQuestionDTO.getOptions());
-                bokQuestionVO.setAnswer(choiceQuestionDTO.getAnswer());
-                bokQuestionVO.setAnalysis(choiceQuestionDTO.getAnalysis());
+                questionVO.setOptions(choiceQuestionDTO.getOptions());
+                questionVO.setAnswer(choiceQuestionDTO.getAnswer());
+                questionVO.setAnalysis(choiceQuestionDTO.getAnalysis());
                 break;
             case TRUE_FALSE:
                 TrueOrFalseQuestionDTO trueOrFalseQuestionDTO = (TrueOrFalseQuestionDTO) baseQuestionDTO;
-                bokQuestionVO.setAnswer(String.valueOf(trueOrFalseQuestionDTO.getAnswer()));
-                bokQuestionVO.setAnalysis(trueOrFalseQuestionDTO.getAnalysis());
+                questionVO.setAnswer(String.valueOf(trueOrFalseQuestionDTO.getAnswer()));
+                questionVO.setAnalysis(trueOrFalseQuestionDTO.getAnalysis());
                 break;
             default:
                 throw HelperException.of(ExceptionType.ERROR, "不支持的题目类型");
         }
-        return bokQuestionVO;
+        return questionVO;
     }
 
     @Data
@@ -157,7 +156,7 @@ public class QuestionApi {
 
         @Data
         private static class Embedded {
-            private List<BokQuestionVO> questions = Collections.emptyList();
+            private List<QuestionVO> questions = Collections.emptyList();
         }
 
         @Data

+ 30 - 0
src/main/java/nju/seec/helper/api/bok/QuestionVO.java

@@ -0,0 +1,30 @@
+package nju.seec.helper.api.bok;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author sheen
+ * <p>
+ * updated by cst
+ */
+@Data
+public class QuestionVO {
+    private String id;
+    private String type;
+    private String stem;
+    private Map<String, String> options;
+    private String answer;
+    @JsonProperty(value = "key_points")
+    private String keyPoints;
+    private String analysis;
+    private List<String> tags;
+    private List<String> knowledgeId;
+    @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
+    private Date lastModified;
+}

+ 5 - 4
src/main/java/nju/seec/helper/vo/EventVO.java → src/main/java/nju/seec/helper/api/dataanalysis/Event.java

@@ -1,9 +1,9 @@
-package nju.seec.helper.vo;
+package nju.seec.helper.api.dataanalysis;
 
+import lombok.AccessLevel;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
-import nju.seec.helper.api.dataanalysis.EventAction;
-import nju.seec.helper.api.dataanalysis.EventType;
 
 import java.time.LocalDateTime;
 
@@ -12,7 +12,8 @@ import java.time.LocalDateTime;
  */
 @Data
 @Accessors(chain = true)
-public class EventVO {
+@NoArgsConstructor(access = AccessLevel.PACKAGE)
+public class Event {
     private String userId;
     private EventType type;
     private LocalDateTime time;

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

@@ -7,8 +7,17 @@ import lombok.Getter;
  */
 @Getter
 public enum EventAction {
+    /**
+     * 参与
+     */
     PARTICIPATE("参与"),
+    /**
+     * 完成
+     */
     COMPLETE("完成"),
+    /**
+     * 创建
+     */
     INITIATE("创建"),
     ;
 

+ 71 - 25
src/main/java/nju/seec/helper/api/dataanalysis/EventApi.java

@@ -1,25 +1,71 @@
-//package nju.seec.helper.api.dataanalysis;
-//
-//import nju.seec.helper.util.RestRequestUtil;
-//import org.springframework.beans.factory.annotation.Value;
-//import org.springframework.scheduling.annotation.Async;
-//import org.springframework.stereotype.Component;
-//
-///**
-// * @author cst
-// */
-//@Component
-//public class EventApi {
-//    @Value("${data-analysis.eventReportUrl}")
-//    private String eventReportUrl;
-//    private final RestRequestUtil restRequestUtil;
-//
-//    public EventApi(RestRequestUtil restRequestUtil) {
-//        this.restRequestUtil = restRequestUtil;
-//    }
-//
-//    @Async
-//    public void reportEvent(EventDTO eventDTO) {
-//        restRequestUtil.sendPostRequest(eventReportUrl, eventDTO, Boolean.class);
-//    }
-//}
+package nju.seec.helper.api.dataanalysis;
+
+import cn.hutool.core.util.StrUtil;
+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.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author cst
+ */
+@Component
+public class EventApi {
+    @Value("${data-analysis.eventReportUrl}")
+    private String eventReportUrl;
+    private final RestRequestUtil restRequestUtil;
+
+    public EventApi(RestRequestUtil restRequestUtil) {
+        this.restRequestUtil = restRequestUtil;
+    }
+
+    @Async
+    public void reportEvent(Event event) {
+        try {
+            System.out.println(event);
+//            restRequestUtil.sendPostRequest(eventReportUrl, event, Boolean.class);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 取得参与课程事件
+     *
+     * @param userId
+     * @param time
+     * @param course
+     * @return
+     */
+    public Event getParticipateClassEvent(String userId, LocalDateTime time, Course course) {
+        return new Event().setUserId(userId).setTime(time).setType(EventType.LESSON).setAction(EventAction.PARTICIPATE).setDescription(course.getName());
+    }
+
+    /**
+     * 取得创建帖子事件
+     *
+     * @param userId
+     * @param time
+     * @param title
+     * @return
+     */
+    public Event getInitPostEvent(String userId, LocalDateTime time, String title) {
+        return new Event().setUserId(userId).setTime(time).setType(EventType.POST).setAction(EventAction.INITIATE).setDescription(title);
+    }
+
+    /**
+     * 取得完成测验事件
+     *
+     * @param userId
+     * @param time
+     * @param quiz
+     * @return
+     */
+    public Event getCompleteQuizEvent(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()));
+    }
+}

+ 3 - 4
src/main/java/nju/seec/helper/aspect/auth/AuthAspect.java

@@ -55,7 +55,7 @@ public class AuthAspect {
 
         Object[] objects = joinPoint.getArgs();
         for (Object o : objects) {
-            if (o.getClass() == LoginUser.class) {
+            if (o instanceof LoginUser) {
                 BeanUtils.copyProperties(user, o);
                 break;
             }
@@ -76,7 +76,6 @@ public class AuthAspect {
         try {
             decodedJWT = jwtVerifier.verify(token);
         } catch (JWTVerificationException e) {
-//            e.printStackTrace();
             throw HelperException.of(ExceptionType.UNAUTHORIZED, "无效Token");
         }
 
@@ -89,7 +88,7 @@ public class AuthAspect {
         UserInfo userInfo = decodedJWT.getClaim("user_info").as(UserInfo.class);
 
         if (userInfo.role == UserRole.ADMIN) {
-            return LoginUser.of(null, UserRole.ADMIN);
+            return LoginUser.of(null, userInfo.id, UserRole.ADMIN);
         }
 
         User user = userService.getUserByPhoneOrPid(userInfo.phone, userInfo.id).orElse(new User());
@@ -102,7 +101,7 @@ public class AuthAspect {
 
         user = userService.createOrUpdateUser(user);
 
-        return LoginUser.of(user.getId(), user.getRole());
+        return LoginUser.of(user.getId(), user.getPid(), user.getRole());
     }
 
     @Data

+ 1 - 0
src/main/java/nju/seec/helper/aspect/auth/LoginUser.java

@@ -13,5 +13,6 @@ import nju.seec.helper.enums.UserRole;
 @NoArgsConstructor
 public class LoginUser {
     private Long id;
+    private Long pid;
     private UserRole role;
 }

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

@@ -0,0 +1,18 @@
+package nju.seec.helper.aspect.event;
+
+import nju.seec.helper.api.dataanalysis.EventAction;
+import nju.seec.helper.api.dataanalysis.EventType;
+
+import java.lang.annotation.*;
+
+/**
+ * @author cst
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Event {
+    EventType type();
+
+    EventAction action();
+}

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

@@ -0,0 +1,110 @@
+package nju.seec.helper.aspect.event;
+
+import com.google.common.collect.ImmutableMap;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+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.aspect.auth.LoginUser;
+import nju.seec.helper.dao.CourseDAO;
+import nju.seec.helper.dao.QuizDAO;
+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.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @author cst
+ */
+@Aspect
+@Component
+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 -> {
+                            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) {
+                                    courseId = (Long) arg;
+                                }
+                            }
+                            eventApi.reportEvent(eventApi.getParticipateClassEvent(String.valueOf(Objects.requireNonNull(user).getPid()), LocalDateTime.now(), courseDAO.findCourseById(courseId)));
+                        })),
+                EventType.POST, ImmutableMap.of(
+                        EventAction.INITIATE, (joinPoint -> {
+                            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) {
+                                    commentDTO = (CommentDTO) arg;
+                                }
+                            }
+                            eventApi.reportEvent(eventApi.getInitPostEvent(String.valueOf(Objects.requireNonNull(user).getPid()), LocalDateTime.now(), Objects.requireNonNull(commentDTO).getTitle()));
+                        })
+                ),
+                EventType.QUIZ, ImmutableMap.of(
+                        EventAction.PARTICIPATE, (joinPoint -> {
+                            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) {
+                                    quizId = (Long) arg;
+                                }
+                            }
+                            eventApi.reportEvent(eventApi.getCompleteQuizEvent(String.valueOf(Objects.requireNonNull(user).getPid()), LocalDateTime.now(), quizDAO.findQuizById(quizId)));
+                        })
+                )
+        );
+    }
+
+    @FunctionalInterface
+    private interface EventHandler {
+        /**
+         * 处理事件
+         *
+         * @param joinPoint
+         */
+        @Transactional(readOnly = true)
+        void handleEvent(JoinPoint joinPoint);
+    }
+
+    @NoArgsConstructor(access = AccessLevel.PRIVATE)
+    private static class DoNothingHandler implements EventHandler {
+        @Getter
+        private static final DoNothingHandler INSTANCE = new DoNothingHandler();
+
+        @Override
+        public void handleEvent(JoinPoint joinPoint) {
+
+        }
+    }
+
+    @SneakyThrows
+    @AfterReturning("@annotation(event)")
+    public void reportEvent(JoinPoint joinPoint, nju.seec.helper.aspect.event.Event event) {
+        eventHandleMap.getOrDefault(event.type(), Collections.emptyMap()).getOrDefault(event.action(), DoNothingHandler.getINSTANCE()).handleEvent(joinPoint);
+    }
+}

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

@@ -1,6 +1,6 @@
 package nju.seec.helper.controller;
 
-import nju.seec.helper.vo.EventVO;
+import nju.seec.helper.api.dataanalysis.Event;
 import nju.seec.helper.service.EventService;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -21,7 +21,7 @@ public class EventController {
     }
 
     @GetMapping
-    public List<EventVO> getAllEvents() {
+    public List<Event> getAllEvents() {
         return eventService.getAllEvents();
     }
 }

+ 0 - 3
src/main/java/nju/seec/helper/controller/UserController.java

@@ -17,9 +17,6 @@ import org.springframework.web.bind.annotation.RestController;
 @RestController
 @RequestMapping("/api/user")
 public class UserController {
-    //    @Value("${helper.user-timeout-seconds}")
-//    private int userTimeoutSeconds;
-//
     private final UserService userService;
 
     public UserController(UserService userService) {

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

@@ -48,10 +48,5 @@ public class User {
     @CreationTimestamp
     private LocalDateTime createAt;
 
-//    @ColumnDefault("'NORMAL'")
-//    @Enumerated(EnumType.STRING)
-//    @Column(nullable = false)
-//    private UserState state;
-
     private Long pid;
 }

+ 12 - 1
src/main/java/nju/seec/helper/enums/QuizState.java

@@ -6,7 +6,18 @@ package nju.seec.helper.enums;
  * updated by cst
  */
 public enum QuizState {
-    NOT_STARTED("未开始"), ONGOING("正在进行"), CLOSED("已结束");
+    /**
+     * 未开始
+     */
+    NOT_STARTED("未开始"),
+    /**
+     * 正在进行
+     */
+    ONGOING("正在进行"),
+    /**
+     * 已结束
+     */
+    CLOSED("已结束");
     private String name;
 
     QuizState(String name) {

+ 20 - 1
src/main/java/nju/seec/helper/enums/SlideState.java

@@ -4,7 +4,26 @@ package nju.seec.helper.enums;
  * @author cst
  */
 public enum SlideState {
-    DRAFT(1), BEFORE_CLASS(2), IN_CLASS(3), AFTER_CLASS(4), FINISH(5);
+    /**
+     * 草稿
+     */
+    DRAFT(1),
+    /**
+     * 课前
+     */
+    BEFORE_CLASS(2),
+    /**
+     * 课中
+     */
+    IN_CLASS(3),
+    /**
+     * 课后
+     */
+    AFTER_CLASS(4),
+    /**
+     * 结束
+     */
+    FINISH(5);
     private Integer num;
 
     SlideState(Integer num) {

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

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

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

@@ -1,6 +1,9 @@
 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;
@@ -39,6 +42,7 @@ 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) {

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

@@ -1,5 +1,6 @@
 package nju.seec.helper.service.impl;
 
+import cn.hutool.core.util.StrUtil;
 import lombok.SneakyThrows;
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.CourseDAO;
@@ -110,7 +111,7 @@ public class CourseFileServiceImpl implements CourseFileService {
     }
 
     private String getCourseFileObjectName(Long courseId, String fileName) {
-        return COURSE_FILE_STORE_DIR + File.separator + courseId + File.separator + fileName;
+        return StrUtil.join(File.separator, COURSE_FILE_STORE_DIR, courseId, fileName);
     }
 
     @Override

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

@@ -1,6 +1,9 @@
 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;
@@ -87,6 +90,7 @@ 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) {

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

@@ -1,18 +1,18 @@
 package nju.seec.helper.service.impl;
 
-import cn.hutool.core.text.StrBuilder;
 import com.google.common.collect.Lists;
-import nju.seec.helper.api.dataanalysis.EventAction;
-import nju.seec.helper.api.dataanalysis.EventType;
+import nju.seec.helper.api.dataanalysis.Event;
+import nju.seec.helper.api.dataanalysis.EventApi;
 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.vo.EventVO;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * @author cst
@@ -25,18 +25,21 @@ public class EventServiceImpl implements EventService {
     private final CommentDAO commentDAO;
     private final QuizStudentAnswerDAO quizStudentAnswerDAO;
 
-    public EventServiceImpl(CourseDAO courseDAO, ChooseDAO chooseDAO, UserDAO userDAO, CommentDAO commentDAO, QuizStudentAnswerDAO quizStudentAnswerDAO) {
+    private final EventApi eventApi;
+
+    public EventServiceImpl(CourseDAO courseDAO, ChooseDAO chooseDAO, UserDAO userDAO, CommentDAO commentDAO, QuizStudentAnswerDAO quizStudentAnswerDAO, EventApi eventApi) {
         this.courseDAO = courseDAO;
         this.chooseDAO = chooseDAO;
         this.userDAO = userDAO;
         this.commentDAO = commentDAO;
         this.quizStudentAnswerDAO = quizStudentAnswerDAO;
+        this.eventApi = eventApi;
     }
 
     @Transactional(readOnly = true)
     @Override
-    public List<EventVO> getAllEvents() {
-        List<EventVO> eventVOList = Lists.newArrayList();
+    public List<Event> getAllEvents() {
+        List<Event> events = Lists.newArrayList();
         List<Course> courses = courseDAO.findAll();
         courses.forEach(course -> {
             List<Choose> chooses = chooseDAO.findByCourseId(course.getId());
@@ -44,13 +47,7 @@ public class EventServiceImpl implements EventService {
                     choose -> {
                         User student = userDAO.findUserById(choose.getStudentId());
                         if (student.getPid() != null) {
-                            eventVOList.add(new EventVO()
-                                    .setUserId(String.valueOf(student.getPid()))
-                                    .setTime(choose.getChooseAt())
-                                    .setType(EventType.LESSON)
-                                    .setAction(EventAction.PARTICIPATE)
-                                    .setDescription(course.getName())
-                            );
+                            events.add(eventApi.getParticipateClassEvent(String.valueOf(Objects.requireNonNull(student).getPid()), LocalDateTime.now(), course));
                         }
                     }
             );
@@ -59,13 +56,7 @@ public class EventServiceImpl implements EventService {
         comments.forEach(comment -> {
             User user = comment.getUser();
             if (user.getPid() != null && user.getRole() == UserRole.STUDENT) {
-                eventVOList.add(new EventVO()
-                        .setUserId(String.valueOf(user.getPid()))
-                        .setTime(comment.getCreateAt())
-                        .setType(EventType.POST)
-                        .setAction(EventAction.INITIATE)
-                        .setDescription(comment.getTitle())
-                );
+                events.add(eventApi.getInitPostEvent(String.valueOf(user.getPid()), comment.getCreateAt(), comment.getTitle()));
             }
         });
         List<QuizStudentAnswer> quizStudentAnswers = quizStudentAnswerDAO.findAll();
@@ -73,16 +64,9 @@ public class EventServiceImpl implements EventService {
             User student = quizStudentAnswer.getStudent();
             if (student.getPid() != null) {
                 Quiz quiz = quizStudentAnswer.getQuiz();
-                Course course = quiz.getCourse();
-                eventVOList.add(new EventVO()
-                        .setUserId(String.valueOf(student.getPid()))
-                        .setTime(quizStudentAnswer.getSubmitAt())
-                        .setType(EventType.QUIZ)
-                        .setAction(EventAction.COMPLETE)
-                        .setDescription(StrBuilder.create().append(course.getName()).append(";").append(quiz.getName()).toStringAndReset())
-                );
+                events.add(eventApi.getCompleteQuizEvent(String.valueOf(student.getPid()), quizStudentAnswer.getSubmitAt(), quiz));
             }
         });
-        return eventVOList;
+        return events;
     }
 }

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

@@ -15,7 +15,7 @@ import nju.seec.helper.enums.UserRole;
 import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.service.QuestionService;
 import nju.seec.helper.vo.question.BaseQuestionVO;
-import nju.seec.helper.vo.question.BokQuestionVO;
+import nju.seec.helper.api.bok.QuestionVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageImpl;
@@ -90,11 +90,11 @@ public class QuestionServiceImpl implements QuestionService {
             questionId = IdUtil.fastSimpleUUID();
         } while (questionRecordDAO.existsByQuestionId(questionId));
 
-        BokQuestionVO bokQuestionVO = questionApi.createQuestion(questionId, baseQuestionDTO);
+        QuestionVO questionVO = questionApi.createQuestion(questionId, baseQuestionDTO);
         questionRecordDAO.save(new QuestionRecord()
                 .setTeacher(userDAO.findUserById(user.getId()))
-                .setQuestionId(bokQuestionVO.getId()));
-        return BaseQuestionVO.convertBokQuestionToVO(bokQuestionVO, true);
+                .setQuestionId(questionVO.getId()));
+        return BaseQuestionVO.convertBokQuestionToVO(questionVO, true);
     }
 
     @Transactional(readOnly = true)
@@ -102,9 +102,9 @@ 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, "您无权修改该问题"));
-        BokQuestionVO bokQuestionVO = questionApi.modifyQuestion(questionId, baseQuestionDTO);
+        QuestionVO questionVO = questionApi.modifyQuestion(questionId, baseQuestionDTO);
         questionRecordDAO.save(questionRecord);
-        return BaseQuestionVO.convertBokQuestionToVO(bokQuestionVO, true);
+        return BaseQuestionVO.convertBokQuestionToVO(questionVO, true);
     }
 
     @Transactional(rollbackFor = Exception.class)

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

@@ -50,7 +50,7 @@ public class QuizServiceImpl implements QuizService {
         this.messageService = messageService;
     }
 
-    private static Map<SlideState, Map<QuizType, QuizState>> MAP = ImmutableMap.of(
+    private static final Map<SlideState, Map<QuizType, QuizState>> MAP = ImmutableMap.of(
             SlideState.DRAFT, ImmutableMap.of(
                     QuizType.BEFORE_CLASS, QuizState.NOT_STARTED,
                     QuizType.IN_CLASS, QuizState.NOT_STARTED,

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

@@ -1,7 +1,10 @@
 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.dao.QuizDAO;
 import nju.seec.helper.dao.QuizStudentAnswerDAO;
 import nju.seec.helper.dao.UserDAO;
@@ -45,6 +48,7 @@ public class QuizStudentAnswerServiceImpl implements QuizStudentAnswerService {
         this.questionService = questionService;
     }
 
+    @Event(type = EventType.QUIZ, action = EventAction.PARTICIPATE)
     @Transactional(rollbackFor = Exception.class)
     @Override
     public QuizStudentAnswerVO submitQuizStudentAnswer(LoginUser user, Long quizId, QuizStudentAnswerDTO quizStudentAnswerDTO) {

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

@@ -1,6 +1,6 @@
 package nju.seec.helper.service.impl;
 
-import cn.hutool.core.text.StrBuilder;
+import cn.hutool.core.util.StrUtil;
 import com.google.common.collect.ImmutableSet;
 import lombok.SneakyThrows;
 import nju.seec.helper.aspect.auth.LoginUser;
@@ -197,16 +197,7 @@ public class SlideServiceImpl implements SlideService {
     }
 
     private String getObjectName(Long slideId, String slideName, String extension) {
-        return StrBuilder
-                .create()
-                .append(SLIDE_STORE_DIR)
-                .append(File.separator)
-                .append(slideId)
-                .append(File.separator)
-                .append(slideName)
-                .append(".")
-                .append(extension)
-                .toStringAndReset();
+        return StrUtil.join(File.separator, SLIDE_STORE_DIR, slideId, slideName + "." + extension);
     }
 
     @Transactional(readOnly = true)

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

@@ -15,7 +15,6 @@ import java.util.Optional;
  */
 @Service
 public class UserServiceImpl implements UserService {
-    //    private final RedisCacheUtils cacheUtils;
     private final UserDAO userDAO;
 
     public UserServiceImpl(UserDAO userDAO

+ 0 - 4
src/main/java/nju/seec/helper/vo/UserVO.java

@@ -19,8 +19,6 @@ public class UserVO {
     private UserRole role;
     private LocalDateTime createAt;
 
-//    private UserState state;
-
     public UserVO(@NonNull User user) {
         this.id = user.getId();
         this.name = user.getName();
@@ -28,7 +26,5 @@ public class UserVO {
         this.phone = user.getPhone();
         this.role = user.getRole();
         this.createAt = user.getCreateAt();
-
-//        this.state = user.getState();
     }
 }

+ 12 - 11
src/main/java/nju/seec/helper/vo/question/BaseQuestionVO.java

@@ -2,6 +2,7 @@ package nju.seec.helper.vo.question;
 
 import lombok.Data;
 import lombok.NonNull;
+import nju.seec.helper.api.bok.QuestionVO;
 import nju.seec.helper.enums.QuestionType;
 
 import java.util.Date;
@@ -23,24 +24,24 @@ public abstract class BaseQuestionVO {
     protected List<String> knowledgeId;
     private Date lastModified;
 
-    public static BaseQuestionVO convertBokQuestionToVO(@NonNull BokQuestionVO bokQuestionVO, boolean withAnswer) {
-        switch (bokQuestionVO.getType()) {
+    public static BaseQuestionVO convertBokQuestionToVO(@NonNull QuestionVO questionVO, boolean withAnswer) {
+        switch (questionVO.getType()) {
             case "choice":
-                return new ChoiceQuestionVO(bokQuestionVO, withAnswer);
+                return new ChoiceQuestionVO(questionVO, withAnswer);
             case "true_false":
-                return new TrueOrFalseQuestionVO(bokQuestionVO, withAnswer);
+                return new TrueOrFalseQuestionVO(questionVO, withAnswer);
             default:
                 return null;
         }
     }
 
-    protected BaseQuestionVO(BokQuestionVO bokQuestionVO) {
-        this.id = bokQuestionVO.getId();
-        this.stem = bokQuestionVO.getStem();
-        this.keyPoints = bokQuestionVO.getKeyPoints();
-        this.tags = bokQuestionVO.getTags();
-        this.knowledgeId = bokQuestionVO.getKnowledgeId();
-        this.lastModified = bokQuestionVO.getLastModified();
+    protected BaseQuestionVO(QuestionVO questionVO) {
+        this.id = questionVO.getId();
+        this.stem = questionVO.getStem();
+        this.keyPoints = questionVO.getKeyPoints();
+        this.tags = questionVO.getTags();
+        this.knowledgeId = questionVO.getKnowledgeId();
+        this.lastModified = questionVO.getLastModified();
     }
 
     /**

+ 6 - 5
src/main/java/nju/seec/helper/vo/question/ChoiceQuestionVO.java

@@ -3,6 +3,7 @@ package nju.seec.helper.vo.question;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NonNull;
+import nju.seec.helper.api.bok.QuestionVO;
 import nju.seec.helper.enums.QuestionType;
 
 import java.util.Map;
@@ -18,14 +19,14 @@ public class ChoiceQuestionVO extends BaseQuestionVO {
     private Map<String, String> options;
     private String answer;
 
-    public ChoiceQuestionVO(@NonNull BokQuestionVO bokQuestionVO, boolean withAnswer) {
-        super(bokQuestionVO);
+    public ChoiceQuestionVO(@NonNull QuestionVO questionVO, boolean withAnswer) {
+        super(questionVO);
         this.type = QuestionType.CHOICE;
-        this.options = bokQuestionVO.getOptions();
+        this.options = questionVO.getOptions();
 
         if (withAnswer) {
-            this.answer = bokQuestionVO.getAnswer();
-            this.analysis = bokQuestionVO.getAnalysis();
+            this.answer = questionVO.getAnswer();
+            this.analysis = questionVO.getAnalysis();
         }
     }
 

+ 5 - 4
src/main/java/nju/seec/helper/vo/question/TrueOrFalseQuestionVO.java

@@ -4,6 +4,7 @@ import cn.hutool.core.convert.Convert;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NonNull;
+import nju.seec.helper.api.bok.QuestionVO;
 import nju.seec.helper.enums.QuestionType;
 
 /**
@@ -22,13 +23,13 @@ public class TrueOrFalseQuestionVO extends BaseQuestionVO {
         return this.answer.equals(Convert.toBool(answer));
     }
 
-    public TrueOrFalseQuestionVO(@NonNull BokQuestionVO bokQuestionVO, boolean withAnswer) {
-        super(bokQuestionVO);
+    public TrueOrFalseQuestionVO(@NonNull QuestionVO questionVO, boolean withAnswer) {
+        super(questionVO);
         this.type = QuestionType.TRUE_FALSE;
 
         if (withAnswer) {
-            this.answer = Boolean.valueOf(bokQuestionVO.getAnswer());
-            this.analysis = bokQuestionVO.getAnalysis();
+            this.answer = Boolean.valueOf(questionVO.getAnswer());
+            this.analysis = questionVO.getAnalysis();
         }
     }
 }

+ 2 - 0
src/main/resources/application-deploy.yml

@@ -1,3 +1,5 @@
+server:
+  port: 9095
 spring:
   datasource:
     url: jdbc:mysql://localhost:13306/helper?setUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8

+ 5 - 5
src/main/resources/application-dev.yml

@@ -10,7 +10,7 @@ spring:
       ddl-auto: update
     database: mysql
     database-platform: org.hibernate.dialect.MySQL8Dialect
-    show-sql: true
+#    show-sql: true
   http:
     encoding:
       force: true
@@ -62,10 +62,10 @@ bok:
   tqIdSearchUrl: ${bok.tqSearchUrl}/findByIdIn?id={id}
   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
+data-analysis:
+  url: http://dataanalysis.seecoder.cn
+  eventUrl: ${data-analysis.url}/api/event
+  eventReportUrl: ${data-analysis.eventUrl}/report
 guavaCache:
   maximumSize: 1000
   expireAfterAccessInSeconds: 0

+ 9 - 9
src/main/resources/hibernate.properties

@@ -1,9 +1,9 @@
-hibernate.format_sql=true
-hibernate.cache.use_second_level_cache=true
-hibernate.cache.use_query_cache=true
-hibernate.cache.region_prefix=helper
-hibernate.cache.region.factory_class=org.hibernate.cache.jcache.internal.JCacheRegionFactory
-hibernate.cache.provider_configuration_file_resource_path=ehcache.xml
-hibernate.cache.use_structured_entries=true
-hibernate.generate_statistics=false
-hibernate.javax.cache.missing_cache_strategy=create
+#hibernate.format_sql=true
+#hibernate.cache.use_second_level_cache=true
+#hibernate.cache.use_query_cache=true
+#hibernate.cache.region_prefix=helper
+#hibernate.cache.region.factory_class=org.hibernate.cache.jcache.internal.JCacheRegionFactory
+#hibernate.cache.provider_configuration_file_resource_path=ehcache.xml
+#hibernate.cache.use_structured_entries=true
+#hibernate.generate_statistics=false
+#hibernate.javax.cache.missing_cache_strategy=create