Browse Source

feat: 增加获取测试记录接口

ChenSiTong 6 năm trước cách đây
mục cha
commit
4b9e6a37bf
38 tập tin đã thay đổi với 437 bổ sung204 xóa
  1. 6 5
      src/main/java/nju/seec/helper/api/bok/KnowledgeApi.java
  2. 6 5
      src/main/java/nju/seec/helper/api/bok/QuestionApi.java
  3. 5 4
      src/main/java/nju/seec/helper/api/bok/QuestionQueryApi.java
  4. 3 7
      src/main/java/nju/seec/helper/api/bok/RecordApi.java
  5. 2 5
      src/main/java/nju/seec/helper/api/bok/UserInteractiveApi.java
  6. 21 0
      src/main/java/nju/seec/helper/api/bok/vo/RecordQuestionVO.java
  7. 8 0
      src/main/java/nju/seec/helper/api/bok/vo/RecordVO.java
  8. 9 9
      src/main/java/nju/seec/helper/api/dataanalysis/EventApi.java
  9. 4 4
      src/main/java/nju/seec/helper/api/dataanalysis/ExerciseApi.java
  10. 21 0
      src/main/java/nju/seec/helper/api/dataanalysis/model/Event.java
  11. 38 0
      src/main/java/nju/seec/helper/api/dataanalysis/model/ExerciseRecord.java
  12. 5 6
      src/main/java/nju/seec/helper/aspect/auth/AuthAspect.java
  13. 22 31
      src/main/java/nju/seec/helper/aspect/event/EventAspect.java
  14. 5 3
      src/main/java/nju/seec/helper/aspect/message/MessageAspect.java
  15. 2 2
      src/main/java/nju/seec/helper/controller/EventController.java
  16. 5 8
      src/main/java/nju/seec/helper/controller/QuestionController.java
  17. 0 1
      src/main/java/nju/seec/helper/controller/QuizController.java
  18. 33 0
      src/main/java/nju/seec/helper/controller/RecordController.java
  19. 8 0
      src/main/java/nju/seec/helper/dao/UserDAO.java
  20. 18 18
      src/main/java/nju/seec/helper/dto/common/LoginDTO.java
  21. 2 2
      src/main/java/nju/seec/helper/service/EventService.java
  22. 25 0
      src/main/java/nju/seec/helper/service/RecordService.java
  23. 3 6
      src/main/java/nju/seec/helper/service/impl/CourseFileServiceImpl.java
  24. 7 7
      src/main/java/nju/seec/helper/service/impl/EventServiceImpl.java
  25. 1 11
      src/main/java/nju/seec/helper/service/impl/NoticeServiceImpl.java
  26. 3 1
      src/main/java/nju/seec/helper/service/impl/QuestionServiceImpl.java
  27. 1 5
      src/main/java/nju/seec/helper/service/impl/QuizServiceImpl.java
  28. 44 0
      src/main/java/nju/seec/helper/service/impl/RecordServiceImpl.java
  29. 1 5
      src/main/java/nju/seec/helper/service/impl/ReplyServiceImpl.java
  30. 3 9
      src/main/java/nju/seec/helper/service/impl/SlideServiceImpl.java
  31. 0 14
      src/main/java/nju/seec/helper/util/Consts.java
  32. 1 1
      src/main/java/nju/seec/helper/util/JsonUtils.java
  33. 106 0
      src/main/java/nju/seec/helper/util/OssUtils.java
  34. 8 4
      src/main/java/nju/seec/helper/util/file/FileUtils.java
  35. 0 30
      src/main/java/nju/seec/helper/util/oss/OssObjectUrlUtils.java
  36. 1 1
      src/main/java/nju/seec/helper/vo/question/BaseQuestionVO.java
  37. 5 0
      src/main/resources/application-deploy.yml
  38. 5 0
      src/main/resources/application-dev.yml

+ 6 - 5
src/main/java/nju/seec/helper/api/bok/KnowledgeApi.java

@@ -2,10 +2,10 @@ package nju.seec.helper.api.bok;
 
 import lombok.AllArgsConstructor;
 import lombok.Data;
-import nju.seec.helper.util.Consts;
+import nju.seec.helper.api.bok.vo.KnowledgeNodeVO;
 import nju.seec.helper.util.RestRequestUtil;
 import nju.seec.helper.util.cache.CaffeineCacheUtils;
-import nju.seec.helper.api.bok.vo.KnowledgeNodeVO;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
@@ -18,7 +18,8 @@ import java.util.List;
 @ConfigurationProperties("bok.knowledge")
 @Data
 public class KnowledgeApi {
-    private static final String BOK_KNOWLEDGE_CACHE_NAME = Consts.BOK_KNOWLEDGE_CACHE_NAME;
+    @Value("${bok.knowledge-cache-name}")
+    private String bokKnowledgeCacheName;
 
     private final RestRequestUtil restRequestUtil;
     private final CaffeineCacheUtils cacheUtils;
@@ -32,12 +33,12 @@ public class KnowledgeApi {
 
     @SuppressWarnings("unchecked")
     public List<KnowledgeNodeVO> getAllKnowledgeNodes() {
-        Object cache = cacheUtils.get(BOK_KNOWLEDGE_CACHE_NAME, "all");
+        Object cache = cacheUtils.get(bokKnowledgeCacheName, "all");
         if (cache instanceof List) {
             return (List<KnowledgeNodeVO>) cache;
         }
         List<KnowledgeNodeVO> knowledgeNodeVOList = restRequestUtil.sendGetRequest(url, List.class);
-        cacheUtils.set(BOK_KNOWLEDGE_CACHE_NAME, "all", knowledgeNodeVOList);
+        cacheUtils.set(bokKnowledgeCacheName, "all", knowledgeNodeVOList);
         return knowledgeNodeVOList;
     }
 

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

@@ -10,10 +10,10 @@ import nju.seec.helper.dto.question.ChoiceQuestionDTO;
 import nju.seec.helper.dto.question.TrueOrFalseQuestionDTO;
 import nju.seec.helper.enums.ExceptionType;
 import nju.seec.helper.exception.HelperException;
-import nju.seec.helper.util.Consts;
 import nju.seec.helper.util.RestRequestUtil;
 import nju.seec.helper.util.StringUtils;
 import nju.seec.helper.util.cache.CaffeineCacheUtils;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageImpl;
@@ -33,7 +33,8 @@ import java.util.stream.Collectors;
 @ConfigurationProperties("bok.question")
 @Data
 public class QuestionApi {
-    private static final String BOK_QUESTION_CACHE_NAME = Consts.BOK_QUESTION_CACHE_NAME;
+    @Value("${bok.question-cache-name}")
+    private String bokQuestionCacheName;
 
     private final RestRequestUtil restRequestUtil;
     private final CaffeineCacheUtils cacheUtils;
@@ -57,17 +58,17 @@ public class QuestionApi {
         PageableQuestionListVO result = restRequestUtil.sendGetRequest(searchUrl, PageableQuestionListVO.class, urlParams);
 
         List<QuestionVO> questionVOList = result.getQuestions();
-        cacheUtils.setAll(BOK_QUESTION_CACHE_NAME, questionVOList.parallelStream().collect(Collectors.toMap(QuestionVO::getId, bokQuestion -> bokQuestion)));
+        cacheUtils.setAll(bokQuestionCacheName, questionVOList.parallelStream().collect(Collectors.toMap(QuestionVO::getId, bokQuestion -> bokQuestion)));
         return new PageImpl<>(questionVOList, pageable, result.getPage().getTotalElements());
     }
 
     public QuestionVO getQuestionsById(String questionId) {
-        Object cachedBokQuestion = cacheUtils.get(BOK_QUESTION_CACHE_NAME, questionId);
+        Object cachedBokQuestion = cacheUtils.get(bokQuestionCacheName, questionId);
         if (cachedBokQuestion instanceof QuestionVO) {
             return (QuestionVO) cachedBokQuestion;
         }
         QuestionVO questionVO = restRequestUtil.sendGetRequest(url + "/" + questionId, QuestionVO.class);
-        cacheUtils.set(BOK_QUESTION_CACHE_NAME, questionVO.getId(), questionVO);
+        cacheUtils.set(bokQuestionCacheName, questionVO.getId(), questionVO);
         return questionVO;
     }
 

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

@@ -6,10 +6,10 @@ import com.google.common.collect.Maps;
 import com.google.gson.reflect.TypeToken;
 import lombok.Data;
 import nju.seec.helper.api.bok.vo.QuestionVO;
-import nju.seec.helper.util.Consts;
 import nju.seec.helper.util.JsonUtils;
 import nju.seec.helper.util.RestRequestUtil;
 import nju.seec.helper.util.cache.CaffeineCacheUtils;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
@@ -24,7 +24,8 @@ import java.util.stream.Collectors;
 @ConfigurationProperties("bok.question-query")
 @Data
 public class QuestionQueryApi {
-    private static final String BOK_QUESTION_CACHE_NAME = Consts.BOK_QUESTION_CACHE_NAME;
+    @Value("${bok.question-cache-name}")
+    private String bokQuestionCacheName;
 
     private final RestRequestUtil restRequestUtil;
     private final CaffeineCacheUtils cacheUtils;
@@ -40,7 +41,7 @@ public class QuestionQueryApi {
     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);
+        final Map<String, QuestionVO> cacheBokQuestionsMap = (Map) cacheUtils.multiGet(bokQuestionCacheName, ids);
         bokQuestionsMap.putAll(cacheBokQuestionsMap);
 
         List<String> requestIds = Lists.newArrayList(ids);
@@ -55,7 +56,7 @@ public class QuestionQueryApi {
             Map<String, QuestionVO> remoteBokQuestionsMap = questionVOList.parallelStream().collect(Collectors.toMap(QuestionVO::getId, bokQuestion -> bokQuestion));
 
             bokQuestionsMap.putAll(remoteBokQuestionsMap);
-            cacheUtils.setAll(BOK_QUESTION_CACHE_NAME, (Map) remoteBokQuestionsMap);
+            cacheUtils.setAll(bokQuestionCacheName, (Map) remoteBokQuestionsMap);
         }
 
         return ids.stream()

+ 3 - 7
src/main/java/nju/seec/helper/api/bok/RecordApi.java

@@ -3,13 +3,10 @@ package nju.seec.helper.api.bok;
 import com.google.common.collect.ImmutableMap;
 import lombok.Data;
 import nju.seec.helper.api.bok.dto.RecordDTO;
-import nju.seec.helper.api.bok.vo.PageableQuestionListVO;
-import nju.seec.helper.api.bok.vo.QuestionVO;
+import nju.seec.helper.api.bok.vo.RecordQuestionVO;
 import nju.seec.helper.util.RestRequestUtil;
 import nju.seec.helper.util.StringUtils;
 import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageImpl;
 import org.springframework.data.domain.Pageable;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
@@ -45,14 +42,13 @@ public class RecordApi {
                 .build();
     }
 
-    public Page<QuestionVO> getCompletedQuestions(String userId, Pageable pageable) {
+    public RecordQuestionVO getUserRecordQuestions(String userId, Pageable pageable) {
         Map<String, ?> uriVariables = ImmutableMap.of(
                 "userId", userId,
                 "page", pageable.getPageNumber() + 1,
                 "size", pageable.getPageSize(),
                 "sort", StringUtils.getSortString(pageable.getSort())
         );
-        PageableQuestionListVO questionListVO = restRequestUtil.sendGetRequest(questionUrl, PageableQuestionListVO.class, uriVariables);
-        return new PageImpl<>(questionListVO.getQuestions(), pageable, questionListVO.getPage().getTotalElements());
+        return restRequestUtil.sendGetRequest(questionUrl, RecordQuestionVO.class, uriVariables);
     }
 }

+ 2 - 5
src/main/java/nju/seec/helper/api/bok/UserInteractiveApi.java

@@ -8,7 +8,6 @@ import nju.seec.helper.api.bok.dto.RateDTO;
 import nju.seec.helper.api.bok.vo.InteractiveVO;
 import nju.seec.helper.api.bok.vo.PageableQuestionListVO;
 import nju.seec.helper.api.bok.vo.QuestionVO;
-import nju.seec.helper.util.Consts;
 import nju.seec.helper.util.JsonUtils;
 import nju.seec.helper.util.RestRequestUtil;
 import nju.seec.helper.util.StringUtils;
@@ -29,8 +28,6 @@ import java.util.Map;
 @ConfigurationProperties("bok.user")
 @Data
 public class UserInteractiveApi {
-    private static final String BOK_QUESTION_CACHE_NAME = Consts.BOK_QUESTION_CACHE_NAME;
-
     private final RestRequestUtil restRequestUtil;
     private final CaffeineCacheUtils cacheUtils;
 
@@ -55,8 +52,8 @@ public class UserInteractiveApi {
     public Page<QuestionVO> getCollectAndRateQuestions(String userId, Boolean collect, Boolean rate, Pageable pageable) {
         Map<String, ?> uriVariables = ImmutableMap.<String, Object>builder()
                 .put("userId", userId)
-                .put("collection", collect)
-                .put("rate", rate)
+                .put("collection", collect == null ? "" : collect)
+                .put("rate", rate == null ? "" : rate)
                 .put("page", pageable.getPageNumber() + 1)
                 .put("size", pageable.getPageSize())
                 .put("sort", StringUtils.getSortString(pageable.getSort()))

+ 21 - 0
src/main/java/nju/seec/helper/api/bok/vo/RecordQuestionVO.java

@@ -0,0 +1,21 @@
+package nju.seec.helper.api.bok.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author bok
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class RecordQuestionVO {
+    PageVO page;
+    List<QuestionVO> questions;
+    List<RecordVO> records;
+}

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

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

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

@@ -4,7 +4,7 @@ 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.api.dataanalysis.model.Event;
 import nju.seec.helper.entity.Course;
 import nju.seec.helper.entity.Quiz;
 import nju.seec.helper.util.RestRequestUtil;
@@ -29,8 +29,8 @@ public class EventApi {
     }
 
     @Async
-    public void reportEvent(EventDTO eventDTO) {
-        System.out.println(eventDTO);
+    public void reportEvent(Event event) {
+        System.out.println(event);
 //            restRequestUtil.sendPostRequest(eventReportUrl, event, Boolean.class);
     }
 
@@ -42,8 +42,8 @@ public class EventApi {
      * @param course
      * @return
      */
-    public EventDTO createParticipateClassEventDTO(String userId, LocalDateTime time, Course course) {
-        return EventDTO.builder()
+    public Event createParticipateClassEventDTO(String userId, LocalDateTime time, Course course) {
+        return Event.builder()
                 .userId(userId).time(time)
                 .type(EventType.LESSON)
                 .action(EventAction.PARTICIPATE)
@@ -59,8 +59,8 @@ public class EventApi {
      * @param title
      * @return
      */
-    public EventDTO createInitPostEventDTO(String userId, LocalDateTime time, String title) {
-        return EventDTO
+    public Event createInitPostEventDTO(String userId, LocalDateTime time, String title) {
+        return Event
                 .builder()
                 .userId(userId)
                 .time(time)
@@ -78,8 +78,8 @@ public class EventApi {
      * @param quiz
      * @return
      */
-    public EventDTO createCompleteQuizEventDTO(String userId, LocalDateTime time, Quiz quiz) {
-        return EventDTO
+    public Event createCompleteQuizEventDTO(String userId, LocalDateTime time, Quiz quiz) {
+        return Event
                 .builder()
                 .userId(userId)
                 .time(time)

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

@@ -1,7 +1,7 @@
 package nju.seec.helper.api.dataanalysis;
 
 import lombok.Data;
-import nju.seec.helper.api.dataanalysis.dto.ExerciseRecordDTO;
+import nju.seec.helper.api.dataanalysis.model.ExerciseRecord;
 import nju.seec.helper.util.RestRequestUtil;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.scheduling.annotation.Async;
@@ -26,13 +26,13 @@ public class ExerciseApi {
     }
 
     @Async
-    public void reportExercise(List<ExerciseRecordDTO> exerciseRecords) {
+    public void reportExercise(List<ExerciseRecord> exerciseRecords) {
         System.out.println(exerciseRecords);
 //        restRequestUtil.sendPostRequest(exerciseReportUrl, exerciseRecords, Boolean.class);
     }
 
-    public ExerciseRecordDTO createExerciseRecordDTO(String userId, String questionId, LocalDateTime startAt, LocalDateTime endAt, Boolean pass) {
-        return ExerciseRecordDTO.builder()
+    public ExerciseRecord createExerciseRecordDTO(String userId, String questionId, LocalDateTime startAt, LocalDateTime endAt, Boolean pass) {
+        return ExerciseRecord.builder()
                 .userId(userId)
                 .questionId(questionId)
                 .startAt(startAt)

+ 21 - 0
src/main/java/nju/seec/helper/api/dataanalysis/model/Event.java

@@ -0,0 +1,21 @@
+package nju.seec.helper.api.dataanalysis.model;
+
+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 Event {
+    private String userId;
+    private EventType type;
+    private LocalDateTime time;
+    private EventAction action;
+    private String description;
+}

+ 38 - 0
src/main/java/nju/seec/helper/api/dataanalysis/model/ExerciseRecord.java

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

+ 5 - 6
src/main/java/nju/seec/helper/aspect/auth/AuthAspect.java

@@ -24,7 +24,6 @@ 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;
@@ -82,11 +81,11 @@ public class AuthAspect {
             throw HelperException.of(ExceptionType.UNAUTHORIZED, "无效Token");
         }
 
-        boolean isExpired = JWT.decode(token).getExpiresAt().before(new Date());
-
-        if (isExpired) {
-            throw HelperException.of(ExceptionType.UNAUTHORIZED, "Token已过期");
-        }
+//        boolean isExpired = JWT.decode(token).getExpiresAt().before(new Date());
+//
+//        if (isExpired) {
+//            throw HelperException.of(ExceptionType.UNAUTHORIZED, "Token已过期");
+//        }
 
         UserInfo userInfo = decodedJWT.getClaim("user_info").as(UserInfo.class);
 

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

@@ -32,38 +32,29 @@ public class EventAspect {
     public EventAspect(CourseDAO courseDAO, QuizDAO quizDAO, EventApi eventApi) {
         this.eventHandleMap = ImmutableMap.of(
                 EventType.LESSON, ImmutableMap.of(
-                        EventAction.PARTICIPATE, ((joinPoint, user) -> {
-                            Stream.of(joinPoint.getArgs())
-                                    .filter(o -> o instanceof Long)
-                                    .map(o -> (Long) o)
-                                    .findFirst()
-                                    .ifPresent(courseId ->
-                                            eventApi.reportEvent(eventApi.createParticipateClassEventDTO(String.valueOf(user.getPid()), LocalDateTime.now(), courseDAO.findCourseById(courseId)))
-                                    )
-                            ;
-                        }))
+                        EventAction.PARTICIPATE, ((joinPoint, user) -> Stream.of(joinPoint.getArgs())
+                                .filter(o -> o instanceof Long)
+                                .map(o -> (Long) o)
+                                .findFirst()
+                                .ifPresent(courseId ->
+                                        eventApi.reportEvent(eventApi.createParticipateClassEventDTO(String.valueOf(user.getPid()), LocalDateTime.now(), courseDAO.findCourseById(courseId)))
+                                )))
                 , EventType.POST, ImmutableMap.of(
-                        EventAction.INITIATE, ((joinPoint, user) -> {
-                            Stream.of(joinPoint.getArgs())
-                                    .filter(o -> o instanceof CommentDTO)
-                                    .map(o -> (CommentDTO) o)
-                                    .findFirst()
-                                    .ifPresent(commentDTO ->
-                                            eventApi.reportEvent(eventApi.createInitPostEventDTO(String.valueOf(user.getPid()), LocalDateTime.now(), Objects.requireNonNull(commentDTO).getTitle()))
-                                    )
-                            ;
-                        }))
+                        EventAction.INITIATE, ((joinPoint, user) -> Stream.of(joinPoint.getArgs())
+                                .filter(o -> o instanceof CommentDTO)
+                                .map(o -> (CommentDTO) o)
+                                .findFirst()
+                                .ifPresent(commentDTO ->
+                                        eventApi.reportEvent(eventApi.createInitPostEventDTO(String.valueOf(user.getPid()), LocalDateTime.now(), Objects.requireNonNull(commentDTO).getTitle()))
+                                )))
                 , EventType.QUIZ, ImmutableMap.of(
-                        EventAction.COMPLETE, ((joinPoint, user) -> {
-                            Stream.of(joinPoint.getArgs())
-                                    .filter(o -> o instanceof Long)
-                                    .map(o -> (Long) o)
-                                    .findFirst()
-                                    .ifPresent(quizId ->
-                                            eventApi.reportEvent(eventApi.createCompleteQuizEventDTO(String.valueOf(user.getPid()), LocalDateTime.now(), quizDAO.findQuizById(quizId)))
-                                    )
-                            ;
-                        }))
+                        EventAction.COMPLETE, ((joinPoint, user) -> Stream.of(joinPoint.getArgs())
+                                .filter(o -> o instanceof Long)
+                                .map(o -> (Long) o)
+                                .findFirst()
+                                .ifPresent(quizId ->
+                                        eventApi.reportEvent(eventApi.createCompleteQuizEventDTO(String.valueOf(user.getPid()), LocalDateTime.now(), quizDAO.findQuizById(quizId)))
+                                )))
         );
     }
 
@@ -75,13 +66,13 @@ public class EventAspect {
          * @param joinPoint 插入点
          * @param user      登录用户
          */
-        @Transactional(readOnly = true)
         void handleEvent(JoinPoint joinPoint, LoginUser user);
     }
 
     private final EventHandler doNothing = (joinPoint, user) -> {
     };
 
+    @Transactional(readOnly = true)
     @SneakyThrows
     @AfterReturning("@annotation(event) && args(user,..)")
     public void reportEvent(JoinPoint joinPoint, Event event, LoginUser user) {

+ 5 - 3
src/main/java/nju/seec/helper/aspect/message/MessageAspect.java

@@ -18,6 +18,7 @@ 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.util.Collections;
 import java.util.List;
@@ -95,10 +96,10 @@ public class MessageAspect {
                     }
 
                     private void sendMessages(Quiz quiz) {
-                        Slide slide = quiz.getSlide();
-                        Course course = slide.getCourse();
-                        Set<Long> chooseStudentIds = chooseDAO.findStudentIdsByCourseId(course.getId());
                         if (quiz.getState() == QuizState.ONGOING) {
+                            Slide slide = quiz.getSlide();
+                            Course course = slide.getCourse();
+                            Set<Long> chooseStudentIds = chooseDAO.findStudentIdsByCourseId(course.getId());
                             messageService.createMessage(chooseStudentIds, MessageType.QUIZ_NEW, String.format("[%s] - [%s] 的测试 [%s] 已开放", course.getName(), slide.getName(), quiz.getName()), quiz.getId());
                         }
                     }
@@ -106,6 +107,7 @@ public class MessageAspect {
                 .build();
     }
 
+    @Transactional(rollbackFor = Exception.class)
     @AfterReturning(value = "@annotation(message)", returning = "object")
     public void createMessage(JoinPoint joinPoint, Message message, Object object) {
         messageCreatorMap.getOrDefault(message.value(), doNothing).createMessage(joinPoint, object);

+ 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.dto.EventDTO;
+import nju.seec.helper.api.dataanalysis.model.Event;
 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<EventDTO> getAllEvents() {
+    public List<Event> getAllEvents() {
         return eventService.getAllEvents();
     }
 }

+ 5 - 8
src/main/java/nju/seec/helper/controller/QuestionController.java

@@ -99,8 +99,7 @@ public class QuestionController {
      */
     @Auth(roles = UserRole.TEACHER, message = "获取创建的题目")
     @GetMapping("/created")
-    public PageResponse<BaseQuestionVO> getCreatedQuestions(LoginUser user,
-                                                            @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
+    public PageResponse<BaseQuestionVO> getCreatedQuestions(LoginUser user, @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
         return PageResponse.of(questionService.getCreatedQuestions(user, pageable));
     }
 
@@ -136,16 +135,14 @@ public class QuestionController {
      */
     @Auth(roles = {UserRole.TEACHER, UserRole.STUDENT}, message = "取得收藏评价的题目")
     @GetMapping("/collected")
-    public PageResponse<BaseQuestionVO> getCollectedQuestions(LoginUser user,
-                                                              @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
-        return PageResponse.of(questionService.getCollectedAndRatedQuestions(user, true, false, pageable));
+    public PageResponse<BaseQuestionVO> getCollectedQuestions(LoginUser user, @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
+        return PageResponse.of(questionService.getCollectedAndRatedQuestions(user, true, null, pageable));
     }
 
     @Auth(roles = {UserRole.TEACHER, UserRole.STUDENT}, message = "取得收藏评价的题目")
     @GetMapping("/rated")
-    public PageResponse<BaseQuestionVO> getRatedQuestions(LoginUser user,
-                                                          @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
-        return PageResponse.of(questionService.getCollectedAndRatedQuestions(user, false, true, pageable));
+    public PageResponse<BaseQuestionVO> getRatedQuestions(LoginUser user, @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
+        return PageResponse.of(questionService.getCollectedAndRatedQuestions(user, null, true, pageable));
     }
 
     /**

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

@@ -29,7 +29,6 @@ import org.springframework.web.bind.annotation.*;
  * @author xst
  * <p>
  * updated by cst
- * @doc http://47.100.18.120:3000/SEEC-BOK/API-DOC/src/master/helper/api/Quiz-%e6%b5%8b%e8%af%95.md
  */
 @RestController
 @RequestMapping("/api/quiz")

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

@@ -0,0 +1,33 @@
+package nju.seec.helper.controller;
+
+import nju.seec.helper.api.dataanalysis.model.ExerciseRecord;
+import nju.seec.helper.service.RecordService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * @author cst
+ */
+@RestController
+@RequestMapping("/api/record")
+public class RecordController {
+    private final RecordService recordService;
+
+    public RecordController(RecordService recordService) {
+        this.recordService = recordService;
+    }
+
+    @GetMapping
+    public List<ExerciseRecord> getAllRecords() {
+        return recordService.getAllRecords();
+    }
+
+    @GetMapping("/{userId}")
+    public List<ExerciseRecord> getRecordsByUserId(@PathVariable String userId) {
+        return recordService.getRecordsByUserId(userId);
+    }
+}

+ 8 - 0
src/main/java/nju/seec/helper/dao/UserDAO.java

@@ -9,6 +9,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.stereotype.Repository;
 
+import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 
@@ -86,4 +87,11 @@ public interface UserDAO extends JpaRepository<User, Long>, JpaSpecificationExec
      * @return
      */
     Page<User> findByIdIn(Set<Long> ids, Pageable pageable);
+
+    /**
+     * 查询所有具有PID的用户
+     *
+     * @return
+     */
+    List<User> findByPidNotNull();
 }

+ 18 - 18
src/main/java/nju/seec/helper/dto/common/LoginDTO.java

@@ -1,18 +1,18 @@
-package nju.seec.helper.dto.common;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotBlank;
-import java.io.Serializable;
-
-/**
- * @author cst
- */
-@Data
-public class LoginDTO implements Serializable {
-    private static final long serialVersionUID = -3359173125836901241L;
-    @NotBlank(message = "登录名不能为空")
-    private String username;
-    @NotBlank(message = "密码不能为空")
-    private String password;
-}
+//package nju.seec.helper.dto.common;
+//
+//import lombok.Data;
+//
+//import javax.validation.constraints.NotBlank;
+//import java.io.Serializable;
+//
+///**
+// * @author cst
+// */
+//@Data
+//public class LoginDTO implements Serializable {
+//    private static final long serialVersionUID = -3359173125836901241L;
+//    @NotBlank(message = "登录名不能为空")
+//    private String username;
+//    @NotBlank(message = "密码不能为空")
+//    private String password;
+//}

+ 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.dto.EventDTO;
+import nju.seec.helper.api.dataanalysis.model.Event;
 
 import java.util.List;
 
@@ -13,5 +13,5 @@ public interface EventService {
      *
      * @return
      */
-    List<EventDTO> getAllEvents();
+    List<Event> getAllEvents();
 }

+ 25 - 0
src/main/java/nju/seec/helper/service/RecordService.java

@@ -0,0 +1,25 @@
+package nju.seec.helper.service;
+
+import nju.seec.helper.api.dataanalysis.model.ExerciseRecord;
+
+import java.util.List;
+
+/**
+ * @author cst
+ */
+public interface RecordService {
+    /**
+     * 取得所有学生的做题记录
+     *
+     * @return
+     */
+    List<ExerciseRecord> getAllRecords();
+
+    /**
+     * 取得某个学生的做题记录
+     *
+     * @param userId
+     * @return
+     */
+    List<ExerciseRecord> getRecordsByUserId(String userId);
+}

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

@@ -12,8 +12,7 @@ import nju.seec.helper.enums.ExceptionType;
 import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.service.CourseFileService;
 import nju.seec.helper.service.CourseService;
-import nju.seec.helper.util.oss.OssObjectUrlUtils;
-import nju.seec.helper.util.oss.OssUtils;
+import nju.seec.helper.util.OssUtils;
 import nju.seec.helper.vo.CourseFileVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -36,14 +35,12 @@ public class CourseFileServiceImpl implements CourseFileService {
     private final CourseService courseService;
 
     private final OssUtils ossUtils;
-    private final OssObjectUrlUtils ossObjectUrlUtils;
 
-    public CourseFileServiceImpl(CourseDAO courseDAO, CourseFileDAO courseFileDAO, CourseService courseService, OssUtils ossUtils, OssObjectUrlUtils ossObjectUrlUtils) {
+    public CourseFileServiceImpl(CourseDAO courseDAO, CourseFileDAO courseFileDAO, CourseService courseService, OssUtils ossUtils) {
         this.courseDAO = courseDAO;
         this.courseFileDAO = courseFileDAO;
         this.courseService = courseService;
         this.ossUtils = ossUtils;
-        this.ossObjectUrlUtils = ossObjectUrlUtils;
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -107,7 +104,7 @@ public class CourseFileServiceImpl implements CourseFileService {
             throw HelperException.of(ExceptionType.FORBIDDEN, "您无权获取该附件的链接");
         }
 
-        return ossObjectUrlUtils.getUrl(courseFile.getObjectName());
+        return ossUtils.getUrl(courseFile.getObjectName());
     }
 
     private String getCourseFileObjectName(Long courseId, String fileName) {

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

@@ -1,8 +1,8 @@
 package nju.seec.helper.service.impl;
 
 import com.google.common.collect.Lists;
-import nju.seec.helper.api.dataanalysis.dto.EventDTO;
 import nju.seec.helper.api.dataanalysis.EventApi;
+import nju.seec.helper.api.dataanalysis.model.Event;
 import nju.seec.helper.dao.*;
 import nju.seec.helper.entity.*;
 import nju.seec.helper.enums.UserRole;
@@ -38,8 +38,8 @@ public class EventServiceImpl implements EventService {
 
     @Transactional(readOnly = true)
     @Override
-    public List<EventDTO> getAllEvents() {
-        List<EventDTO> eventDTOS = 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());
@@ -47,7 +47,7 @@ public class EventServiceImpl implements EventService {
                     choose -> {
                         User student = userDAO.findUserById(choose.getStudentId());
                         if (student.getPid() != null) {
-                            eventDTOS.add(eventApi.createParticipateClassEventDTO(String.valueOf(Objects.requireNonNull(student).getPid()), LocalDateTime.now(), course));
+                            events.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) {
-                eventDTOS.add(eventApi.createInitPostEventDTO(String.valueOf(user.getPid()), comment.getCreateAt(), comment.getTitle()));
+                events.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();
-                eventDTOS.add(eventApi.createCompleteQuizEventDTO(String.valueOf(student.getPid()), quizStudentAnswer.getSubmitAt(), quiz));
+                events.add(eventApi.createCompleteQuizEventDTO(String.valueOf(student.getPid()), quizStudentAnswer.getSubmitAt(), quiz));
             }
         });
-        return eventDTOS;
+        return events;
     }
 }

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

@@ -2,16 +2,13 @@ package nju.seec.helper.service.impl;
 
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.aspect.message.Message;
-import nju.seec.helper.dao.ChooseDAO;
 import nju.seec.helper.dao.CourseDAO;
 import nju.seec.helper.dao.NoticeDAO;
 import nju.seec.helper.dto.notice.NoticeDTO;
-import nju.seec.helper.entity.Course;
 import nju.seec.helper.entity.Notice;
 import nju.seec.helper.enums.ExceptionType;
 import nju.seec.helper.enums.MessageType;
 import nju.seec.helper.exception.HelperException;
-import nju.seec.helper.service.MessageService;
 import nju.seec.helper.service.NoticeService;
 import nju.seec.helper.vo.NoticeVO;
 import org.springframework.data.domain.Page;
@@ -25,24 +22,17 @@ import org.springframework.transaction.annotation.Transactional;
 @Service
 public class NoticeServiceImpl implements NoticeService {
     private final CourseDAO courseDAO;
-    private final ChooseDAO chooseDAO;
     private final NoticeDAO noticeDAO;
 
-    private final MessageService messageService;
-
-    public NoticeServiceImpl(CourseDAO courseDAO, ChooseDAO chooseDAO, NoticeDAO noticeDAO, MessageService messageService) {
+    public NoticeServiceImpl(CourseDAO courseDAO, NoticeDAO noticeDAO) {
         this.courseDAO = courseDAO;
-        this.chooseDAO = chooseDAO;
         this.noticeDAO = noticeDAO;
-        this.messageService = messageService;
     }
 
     @Message(MessageType.NOTICE_NEW)
     @Transactional(rollbackFor = Exception.class)
     @Override
     public NoticeVO createNotice(LoginUser user, NoticeDTO noticeDTO) {
-        Course course = courseDAO.findCourseById(noticeDTO.getCourseId());
-
         Notice notice = new Notice()
                 .setCourseId(noticeDTO.getCourseId())
                 .setTeacherId(user.getId())

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

@@ -9,6 +9,7 @@ import nju.seec.helper.api.bok.dto.CollectDTO;
 import nju.seec.helper.api.bok.dto.RateDTO;
 import nju.seec.helper.api.bok.vo.InteractiveVO;
 import nju.seec.helper.api.bok.vo.QuestionVO;
+import nju.seec.helper.api.bok.vo.RecordQuestionVO;
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.ChooseDAO;
 import nju.seec.helper.dao.QuestionRecordDAO;
@@ -178,7 +179,8 @@ public class QuestionServiceImpl implements QuestionService {
     @Transactional(readOnly = true)
     @Override
     public Page<BaseQuestionVO> getCompletedQuestions(LoginUser user, Pageable pageable) {
-        return convert(user, recordApi.getCompletedQuestions(String.valueOf(user.getPid()), pageable));
+        RecordQuestionVO recordQuestionVO = recordApi.getUserRecordQuestions(String.valueOf(user.getPid()), pageable);
+        return convert(user, new PageImpl<>(recordQuestionVO.getQuestions(), pageable, recordQuestionVO.getPage().getTotalElements()));
     }
 
     @Override

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

@@ -10,7 +10,6 @@ import nju.seec.helper.entity.Quiz;
 import nju.seec.helper.entity.Slide;
 import nju.seec.helper.enums.*;
 import nju.seec.helper.exception.HelperException;
-import nju.seec.helper.service.MessageService;
 import nju.seec.helper.service.QuizService;
 import nju.seec.helper.vo.quiz.QuizVO;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -38,16 +37,13 @@ public class QuizServiceImpl implements QuizService {
     private final ChooseDAO chooseDAO;
     private final UserDAO userDAO;
 
-    private final MessageService messageService;
-
     @Autowired
-    public QuizServiceImpl(SlideDAO slideDAO, QuizDAO quizDAO, QuizStudentAnswerDAO quizStudentAnswerDAO, ChooseDAO chooseDAO, UserDAO userDAO, MessageService messageService) {
+    public QuizServiceImpl(SlideDAO slideDAO, QuizDAO quizDAO, QuizStudentAnswerDAO quizStudentAnswerDAO, ChooseDAO chooseDAO, UserDAO userDAO) {
         this.slideDAO = slideDAO;
         this.quizDAO = quizDAO;
         this.quizStudentAnswerDAO = quizStudentAnswerDAO;
         this.chooseDAO = chooseDAO;
         this.userDAO = userDAO;
-        this.messageService = messageService;
     }
 
     private static final Map<SlideState, Map<QuizType, QuizState>> MAP = ImmutableMap.of(

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

@@ -0,0 +1,44 @@
+package nju.seec.helper.service.impl;
+
+import nju.seec.helper.api.bok.RecordApi;
+import nju.seec.helper.api.bok.vo.RecordQuestionVO;
+import nju.seec.helper.api.dataanalysis.ExerciseApi;
+import nju.seec.helper.api.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;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author cst
+ */
+@Service
+public class RecordServiceImpl implements RecordService {
+    private final UserDAO userDAO;
+
+    private final RecordApi recordApi;
+    private final ExerciseApi exerciseApi;
+
+    public RecordServiceImpl(UserDAO userDAO, RecordApi recordApi, ExerciseApi exerciseApi) {
+        this.userDAO = userDAO;
+        this.recordApi = recordApi;
+        this.exerciseApi = exerciseApi;
+    }
+
+    @Override
+    public List<ExerciseRecord> getAllRecords() {
+        return userDAO.findByPidNotNull()
+                .stream()
+                .flatMap(user -> getRecordsByUserId(String.valueOf(user.getPid())).stream())
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public List<ExerciseRecord> getRecordsByUserId(String userId) {
+        RecordQuestionVO recordQuestionVO = recordApi.getUserRecordQuestions(userId, PageRequest.of(0, Integer.MAX_VALUE));
+        return recordQuestionVO.getRecords().stream().map(recordVO -> exerciseApi.createExerciseRecordDTO(recordVO.getUserId(), recordVO.getQuestionId(), recordVO.getStartAt(), recordVO.getFinishAt(), recordVO.getPass())).collect(Collectors.toList());
+    }
+}

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

@@ -13,7 +13,6 @@ import nju.seec.helper.entity.Reply;
 import nju.seec.helper.enums.ExceptionType;
 import nju.seec.helper.enums.MessageType;
 import nju.seec.helper.exception.HelperException;
-import nju.seec.helper.service.MessageService;
 import nju.seec.helper.service.ReplyService;
 import nju.seec.helper.vo.ReplyVO;
 import org.springframework.stereotype.Service;
@@ -30,14 +29,11 @@ public class ReplyServiceImpl implements ReplyService {
     private final ReplyDAO replyDAO;
     private final UserDAO userDAO;
 
-    private final MessageService messageService;
-
-    public ReplyServiceImpl(SlideDAO slideDAO, CommentDAO commentDAO, ReplyDAO replyDAO, UserDAO userDAO, MessageService messageService) {
+    public ReplyServiceImpl(SlideDAO slideDAO, CommentDAO commentDAO, ReplyDAO replyDAO, UserDAO userDAO) {
         this.slideDAO = slideDAO;
         this.commentDAO = commentDAO;
         this.replyDAO = replyDAO;
         this.userDAO = userDAO;
-        this.messageService = messageService;
     }
 
     @Message(MessageType.COMMENT_REPLY)

+ 3 - 9
src/main/java/nju/seec/helper/service/impl/SlideServiceImpl.java

@@ -19,13 +19,11 @@ import nju.seec.helper.enums.MessageType;
 import nju.seec.helper.enums.SlideState;
 import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.service.CourseService;
-import nju.seec.helper.service.MessageService;
 import nju.seec.helper.service.QuizService;
 import nju.seec.helper.service.SlideService;
+import nju.seec.helper.util.OssUtils;
 import nju.seec.helper.util.file.FileInfo;
 import nju.seec.helper.util.file.FileUtils;
-import nju.seec.helper.util.oss.OssObjectUrlUtils;
-import nju.seec.helper.util.oss.OssUtils;
 import nju.seec.helper.vo.SlideVO;
 import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.data.domain.Page;
@@ -51,21 +49,17 @@ public class SlideServiceImpl implements SlideService {
     private final UserDAO userDAO;
 
     private final OssUtils ossUtils;
-    private final OssObjectUrlUtils ossObjectUrlUtils;
 
     private final QuizService quizService;
-    private final MessageService messageService;
     private final CourseService courseService;
 
-    public SlideServiceImpl(CourseDAO courseDAO, SlideDAO slideDAO, ChooseDAO chooseDAO, UserDAO userDAO, OssUtils ossUtils, OssObjectUrlUtils ossObjectUrlUtils, QuizService quizService, MessageService messageService, CourseService courseService) {
+    public SlideServiceImpl(CourseDAO courseDAO, SlideDAO slideDAO, ChooseDAO chooseDAO, UserDAO userDAO, OssUtils ossUtils, QuizService quizService, CourseService courseService) {
         this.courseDAO = courseDAO;
         this.slideDAO = slideDAO;
         this.chooseDAO = chooseDAO;
         this.userDAO = userDAO;
         this.ossUtils = ossUtils;
-        this.ossObjectUrlUtils = ossObjectUrlUtils;
         this.quizService = quizService;
-        this.messageService = messageService;
         this.courseService = courseService;
     }
 
@@ -232,7 +226,7 @@ public class SlideServiceImpl implements SlideService {
             throw HelperException.of(ExceptionType.FORBIDDEN, "您无权获取该课件的链接");
         }
 
-        return ossObjectUrlUtils.getUrl(slide.getObjectName());
+        return ossUtils.getUrl(slide.getObjectName());
     }
 
     @Transactional(readOnly = true)

+ 0 - 14
src/main/java/nju/seec/helper/util/Consts.java

@@ -1,14 +0,0 @@
-package nju.seec.helper.util;
-
-/**
- * @author cst
- */
-public class Consts {
-    public static final String SYS_NAME = "helper";
-    public static final String BOK_QUESTION_CACHE_NAME = Consts.SYS_NAME + "_bok_question";
-    public static final String BOK_KNOWLEDGE_CACHE_NAME = Consts.SYS_NAME + "_bok_knowledge";
-
-//    public static final String SESSION_USER_NAME = SYS_NAME + "_user";
-//    public static final String EMAIL_CACHE_NAME = SYS_NAME + "_email";
-//    public static final String PHONE_CACHE_NAME = SYS_NAME + "_phone";
-}

+ 1 - 1
src/main/java/nju/seec/helper/util/JsonUtils.java

@@ -15,7 +15,7 @@ import java.time.format.DateTimeFormatter;
  */
 @UtilityClass
 public class JsonUtils {
-    private static final Gson GSON = new GsonBuilder()
+    private final Gson GSON = new GsonBuilder()
             .setPrettyPrinting()
             .registerTypeAdapter(LocalDateTime.class, (JsonSerializer<LocalDateTime>) (localDateTime, type, jsonSerializationContext) -> new JsonPrimitive(localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)))
             .create();

+ 106 - 0
src/main/java/nju/seec/helper/util/OssUtils.java

@@ -0,0 +1,106 @@
+package nju.seec.helper.util;
+
+import com.aliyun.oss.ClientException;
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.OSSException;
+import com.aliyun.oss.model.PutObjectRequest;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.exception.HelperException;
+import nju.seec.helper.util.cache.CaffeineCacheUtils;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Date;
+
+/**
+ * use oss
+ *
+ * @author cst
+ */
+@Slf4j
+@Component
+@Data
+@ConfigurationProperties("aliyun.oss")
+public class OssUtils {
+    private String endpoint;
+    private String accessKeyId;
+    private String accessKeySecret;
+    private String bucketName;
+    private long objectUrlLivingSeconds;
+    private String objectUrlCacheName;
+    private final CaffeineCacheUtils cacheUtils;
+
+    public void upload(String objectName, InputStream inputStream) {
+        OSS ossClient = getOss();
+        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
+        try {
+            ossClient.putObject(putObjectRequest);
+        } catch (OSSException | ClientException e) {
+            log.error(e.getMessage());
+            throw HelperException.of(ExceptionType.ERROR, "文件上传失败");
+        } finally {
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+    }
+
+    public String getUrl(String objectName) {
+        String cacheUrl = (String) cacheUtils.get(objectUrlCacheName, objectName);
+        if (cacheUrl != null) {
+            return cacheUrl;
+        }
+        OSS ossClient = getOss();
+        Date expiration = new Date(System.currentTimeMillis() + objectUrlLivingSeconds * 1000);
+        try {
+            URL url = ossClient.generatePresignedUrl(bucketName, objectName, expiration);
+            return url.toString();
+        } catch (OSSException | ClientException e) {
+            log.error(e.getMessage());
+            throw HelperException.of(ExceptionType.ERROR, "获取文件链接失败");
+        } finally {
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+    }
+
+    public void copy(String sourceObjectName, String destinationObjectName) {
+        OSS ossClient = getOss();
+        try {
+            ossClient.copyObject(bucketName, sourceObjectName, bucketName, destinationObjectName);
+        } catch (OSSException | ClientException e) {
+            log.error(e.getMessage());
+            throw HelperException.of(ExceptionType.ERROR, "文件复制失败");
+        } finally {
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+    }
+
+    @Async
+    public void delete(String objectName) {
+        OSS ossClient = getOss();
+        try {
+            ossClient.deleteObject(bucketName, objectName);
+        } catch (OSSException | ClientException e) {
+            log.error(e.getMessage());
+            throw HelperException.of(ExceptionType.ERROR, "文件删除失败");
+        } finally {
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+    }
+
+    private OSS getOss() {
+        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
+    }
+}

+ 8 - 4
src/main/java/nju/seec/helper/util/file/FileUtils.java

@@ -2,12 +2,12 @@ package nju.seec.helper.util.file;
 
 import cn.hutool.core.io.FileTypeUtil;
 import com.google.common.collect.ImmutableMap;
+import lombok.Cleanup;
 import lombok.SneakyThrows;
 import lombok.experimental.UtilityClass;
 import nju.seec.helper.enums.ExceptionType;
 import nju.seec.helper.exception.HelperException;
 import org.apache.pdfbox.pdmodel.PDDocument;
-import org.apache.pdfbox.text.PDFTextStripper;
 import org.apache.tomcat.util.http.fileupload.IOUtils;
 
 import java.io.ByteArrayInputStream;
@@ -22,8 +22,12 @@ import java.util.Optional;
  */
 @UtilityClass
 public class FileUtils {
-    private static final Map<String, FileInfoParser> FILE_INFO_PARSER_MAP = ImmutableMap.of(
-            "pdf", inputStream -> FileInfo.of(PDDocument.load(inputStream).getNumberOfPages(), "pdf")
+    private final Map<String, FileInfoParser> FILE_INFO_PARSER_MAP = ImmutableMap.of(
+            "pdf", inputStream -> {
+                try (PDDocument pdDocument = PDDocument.load(inputStream)) {
+                    return FileInfo.of(pdDocument.getNumberOfPages(), "pdf");
+                }
+            }
     );
 
 //    private static final Map<String, FileContentParser> FILE_CONTENT_PARSER_MAP = ImmutableMap.of(
@@ -32,7 +36,7 @@ public class FileUtils {
 
     @SneakyThrows
     public FileInfo getFileInfo(InputStream inputStream) {
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        @Cleanup ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
         IOUtils.copy(inputStream, byteArrayOutputStream);
         return Optional.ofNullable(FILE_INFO_PARSER_MAP.get(FileTypeUtil.getType(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()))))
                 .orElseThrow(() -> HelperException.of(ExceptionType.FORBIDDEN, "不支持的文件类型"))

+ 0 - 30
src/main/java/nju/seec/helper/util/oss/OssObjectUrlUtils.java

@@ -1,30 +0,0 @@
-package nju.seec.helper.util.oss;
-
-import nju.seec.helper.util.Consts;
-import nju.seec.helper.util.cache.CaffeineCacheUtils;
-import org.springframework.stereotype.Component;
-
-/**
- * @author cst
- */
-@Component
-public class OssObjectUrlUtils {
-    private static final String OSS_OBJECT_URL_NAME = Consts.SYS_NAME + "_oss_object_url";
-    private static final long OSS_OBJECT_URL_LIVING_SECONDS = 20 * 60;
-    private final CaffeineCacheUtils cacheUtils;
-    private final OssUtils ossUtils;
-
-    public OssObjectUrlUtils(CaffeineCacheUtils cacheUtils, OssUtils ossUtils) {
-        this.cacheUtils = cacheUtils;
-        this.ossUtils = ossUtils;
-    }
-
-    public String getUrl(String objectName) {
-        String url = (String) cacheUtils.get(OSS_OBJECT_URL_NAME, objectName);
-        if (url == null) {
-            url = ossUtils.getUrl(objectName, OSS_OBJECT_URL_LIVING_SECONDS);
-            cacheUtils.set(OSS_OBJECT_URL_NAME, objectName, url);
-        }
-        return url;
-    }
-}

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

@@ -2,8 +2,8 @@ package nju.seec.helper.vo.question;
 
 import lombok.Data;
 import lombok.NonNull;
-import nju.seec.helper.api.bok.vo.QuestionVO;
 import nju.seec.helper.api.bok.constant.QuestionType;
+import nju.seec.helper.api.bok.vo.QuestionVO;
 
 import java.util.Date;
 import java.util.List;

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

@@ -51,6 +51,8 @@ aliyun:
     accessKeyId: LTAI4Fi1qmL4iuhH8t7G7r2H
     accessKeySecret: 7tJWyQqa0cMkQGaPMMvtH6c0VLiOO2
     bucketName: seec-helper-pdf
+    object-url-living-seconds: 1200
+    object-url-cache-name: ${helper.sys-name}_oss_object_url
 #  sms:
 #    accessKeyId: LTAI4FiAXXyNfYJLweBXpKPx
 #    accessSecret: IBoC7KGkZHOvxZQIfQgp98ZY48AVjW
@@ -58,6 +60,8 @@ aliyun:
 #    templateCode: SMS_181555598
 bok:
   api-url: http://120.78.159.171:6060/api
+  question-cache-name: ${helper.sys-name}_bok_question
+  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}
@@ -87,6 +91,7 @@ jwt:
   secret: SEEC-1919810-OIDC-114514
 # 自定义数据
 helper:
+  sys-name: helper
   #  mail:
   #    from: ${spring.mail.username}
   #    subject: seecoder

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

@@ -49,6 +49,8 @@ aliyun:
     accessKeyId: LTAI4Fi1qmL4iuhH8t7G7r2H
     accessKeySecret: 7tJWyQqa0cMkQGaPMMvtH6c0VLiOO2
     bucketName: seec-helper-dev
+    object-url-living-seconds: 1200
+    object-url-cache-name: ${helper.sys-name}_oss_object_url
 #  sms:
 #    accessKeyId: LTAI4FiAXXyNfYJLweBXpKPx
 #    accessSecret: IBoC7KGkZHOvxZQIfQgp98ZY48AVjW
@@ -56,6 +58,8 @@ aliyun:
 #    templateCode: SMS_181555598
 bok:
   api-url: http://120.78.159.171:6060/api
+  question-cache-name: ${helper.sys-name}_bok_question
+  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}
@@ -85,6 +89,7 @@ jwt:
   secret: SEEC-1919810-OIDC-114514
 # 自定义数据
 helper:
+  sys-name: helper
   #  mail:
   #    from: ${spring.mail.username}
   #    subject: seecoder