Explorar el Código

feat: 增加缓存机制,增加教师根据测试状态获取测试接口

ChenSiTong hace 6 años
padre
commit
b8e8f3bd9a

+ 13 - 1
pom.xml

@@ -10,7 +10,7 @@
     </parent>
     <groupId>cn.seecoder</groupId>
     <artifactId>helper-backend</artifactId>
-    <version>4.0.2</version>
+    <version>4.1.2</version>
     <packaging>jar</packaging>
     <name>helper</name>
     <description>backend</description>
@@ -64,6 +64,10 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-websocket</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
@@ -117,6 +121,14 @@
             <artifactId>hibernate-types-52</artifactId>
             <version>2.9.5</version>
         </dependency>
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-jcache</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.ehcache</groupId>
+            <artifactId>ehcache</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 2 - 0
src/main/java/nju/seec/helper/HelperApplication.java

@@ -2,6 +2,7 @@ package nju.seec.helper;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
@@ -9,6 +10,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
 /**
  * @author cst
  */
+@EnableCaching
 @EnableTransactionManagement
 @EnableAsync
 @EnableJpaAuditing

+ 9 - 4
src/main/java/nju/seec/helper/controller/QuizController.java

@@ -11,7 +11,7 @@ import nju.seec.helper.enums.QuizState;
 import nju.seec.helper.enums.UserType;
 import nju.seec.helper.service.QuizService;
 import nju.seec.helper.service.QuizStudentAnswerService;
-import nju.seec.helper.vo.QuizStudentAnswerStatisticVO;
+import nju.seec.helper.vo.quiz.QuizStudentAnswerStatisticVO;
 import nju.seec.helper.vo.quiz.QuizStudentAnswerVO;
 import nju.seec.helper.vo.quiz.QuizVO;
 import org.springframework.data.domain.Pageable;
@@ -60,13 +60,15 @@ public class QuizController {
     /**
      * 基于状态获取测试列表
      */
-    @Auth(roles = UserType.STUDENT, message = "获取测试列表")
+    @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取测试列表")
     @GetMapping("/state")
     public PageResponse<QuizVO> getQuizzesByState(LoginUser user,
                                                   QuizState state,
                                                   @RequestParam(required = false, defaultValue = "") String key,
                                                   @PageableDefault(Integer.MAX_VALUE) Pageable pageable) {
         switch (user.getType()) {
+            case TEACHER:
+                return PageResponse.of(quizService.teacherGetQuizzesByState(user, state, key, pageable));
             case STUDENT:
                 return PageResponse.of(quizService.studentGetQuizzesByState(user, state, key, pageable));
             default:
@@ -124,8 +126,11 @@ public class QuizController {
      */
     @Auth(roles = {UserType.TEACHER}, message = "获取学生作答")
     @GetMapping("/{quizId}/student-answers")
-    public PageResponse<QuizStudentAnswerVO> getQuizStudentAnswers(LoginUser user, @PathVariable Long quizId, @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
-        return PageResponse.of(quizStudentAnswerService.getQuizStudentAnswers(user, quizId, pageable));
+    public PageResponse<QuizStudentAnswerVO> getQuizStudentAnswers(LoginUser user,
+                                                                   @PathVariable Long quizId,
+                                                                   @RequestParam(required = false, defaultValue = "") String key,
+                                                                   @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
+        return PageResponse.of(quizStudentAnswerService.getQuizStudentAnswers(user, quizId, key, pageable));
     }
 
     /**

+ 12 - 0
src/main/java/nju/seec/helper/dao/QuizDAO.java

@@ -2,6 +2,7 @@ package nju.seec.helper.dao;
 
 import nju.seec.helper.entity.Quiz;
 import nju.seec.helper.entity.Slide;
+import nju.seec.helper.entity.User;
 import nju.seec.helper.enums.ExceptionType;
 import nju.seec.helper.enums.QuizState;
 import nju.seec.helper.exception.HelperException;
@@ -82,4 +83,15 @@ public interface QuizDAO extends JpaRepository<Quiz, Long>, JpaSpecificationExec
      * @return
      */
     Page<Quiz> findBySlideAndNameContains(Slide slide, String name, Pageable pageable);
+
+    /**
+     * 根据教师、状态、测试名称检索
+     *
+     * @param teacher
+     * @param quizState
+     * @param name
+     * @param pageable
+     * @return
+     */
+    Page<Quiz> findByTeacherAndStateAndNameContains(User teacher, QuizState quizState, String name, Pageable pageable);
 }

+ 16 - 3
src/main/java/nju/seec/helper/dao/QuizStudentAnswerDAO.java

@@ -5,9 +5,13 @@ import nju.seec.helper.entity.QuizStudentAnswer;
 import nju.seec.helper.entity.User;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.domain.Specification;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.stereotype.Repository;
+import org.springframework.util.StringUtils;
 
+import javax.persistence.criteria.JoinType;
 import java.util.List;
 import java.util.Optional;
 
@@ -17,7 +21,7 @@ import java.util.Optional;
  * updated by cst
  */
 @Repository
-public interface QuizStudentAnswerDAO extends JpaRepository<QuizStudentAnswer, Long> {
+public interface QuizStudentAnswerDAO extends JpaRepository<QuizStudentAnswer, Long>, JpaSpecificationExecutor<QuizStudentAnswer> {
     /**
      * 检查测试是否已有提交
      *
@@ -38,11 +42,20 @@ public interface QuizStudentAnswerDAO extends JpaRepository<QuizStudentAnswer, L
     /**
      * 根据测试分页检索
      *
-     * @param quiz
+     * @param quizId
+     * @param key
      * @param pageable
      * @return
      */
-    Page<QuizStudentAnswer> findByQuiz(Quiz quiz, Pageable pageable);
+    default Page<QuizStudentAnswer> findByQuizIdAndKey(Long quizId, String key, Pageable pageable) {
+        return this.findAll(
+                (Specification<QuizStudentAnswer>) (root, criteriaQuery, criteriaBuilder) ->
+                        StringUtils.hasText(key) ?
+                                criteriaBuilder.and(criteriaBuilder.equal(root.get("quiz").get("id"), quizId), criteriaBuilder.like(root.join("student", JoinType.LEFT).get("name"), nju.seec.helper.util.StringUtils.keyPattern(key))) :
+                                criteriaBuilder.equal(root.get("quiz").get("id"), quizId)
+                , pageable
+        );
+    }
 
     /**
      * 根据测试检索

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

@@ -21,6 +21,8 @@ import java.time.LocalDateTime;
 @DynamicUpdate
 @DynamicInsert
 @Where(clause = "delete_at = 0")
+@Cacheable
+@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
 public class Course {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)

+ 2 - 0
src/main/java/nju/seec/helper/entity/Quiz.java

@@ -26,6 +26,8 @@ import java.util.List;
 @DynamicInsert
 @DynamicUpdate
 @Where(clause = "delete_at = 0 and slide_id in (select s.id from slide s where s.delete_at = 0) and course_id in (select c.id from course c where c.delete_at = 0)")
+@Cacheable
+@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
 public class Quiz {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)

+ 2 - 0
src/main/java/nju/seec/helper/entity/Slide.java

@@ -22,6 +22,8 @@ import java.time.LocalDateTime;
 @DynamicUpdate
 @DynamicInsert
 @Where(clause = "delete_at = 0 and course_id in (select c.id from course c where c.delete_at = 0)")
+@Cacheable
+@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
 public class Slide {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)

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

@@ -4,11 +4,10 @@ import lombok.Data;
 import lombok.experimental.Accessors;
 import nju.seec.helper.enums.UserState;
 import nju.seec.helper.enums.UserType;
-import org.hibernate.annotations.ColumnDefault;
-import org.hibernate.annotations.CreationTimestamp;
-import org.hibernate.annotations.DynamicInsert;
-import org.hibernate.annotations.DynamicUpdate;
+import org.hibernate.annotations.*;
 
+import javax.persistence.Entity;
+import javax.persistence.Table;
 import javax.persistence.*;
 import java.time.LocalDateTime;
 
@@ -22,6 +21,8 @@ import java.time.LocalDateTime;
         @UniqueConstraint(name = "user_email_unique", columnNames = "email")})
 @DynamicUpdate
 @DynamicInsert
+@Cacheable
+@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
 public class User {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)

+ 11 - 0
src/main/java/nju/seec/helper/service/QuizService.java

@@ -92,4 +92,15 @@ public interface QuizService extends AuthService<Quiz> {
      * @param slide
      */
     void modifyQuizState(Slide slide);
+
+    /**
+     * 教师基于状态获取测试
+     *
+     * @param user
+     * @param state
+     * @param key
+     * @param pageable
+     * @return
+     */
+    Page<QuizVO> teacherGetQuizzesByState(LoginUser user, QuizState state, String key, Pageable pageable);
 }

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

@@ -2,7 +2,7 @@ package nju.seec.helper.service;
 
 import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.quiz.QuizStudentAnswerDTO;
-import nju.seec.helper.vo.QuizStudentAnswerStatisticVO;
+import nju.seec.helper.vo.quiz.QuizStudentAnswerStatisticVO;
 import nju.seec.helper.vo.quiz.QuizStudentAnswerVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -26,10 +26,11 @@ public interface QuizStudentAnswerService {
      *
      * @param user
      * @param quizId
+     * @param key
      * @param pageable
      * @return
      */
-    Page<QuizStudentAnswerVO> getQuizStudentAnswers(LoginUser user, Long quizId, Pageable pageable);
+    Page<QuizStudentAnswerVO> getQuizStudentAnswers(LoginUser user, Long quizId, String key, Pageable pageable);
 
     /**
      * 学生查看自己的答案

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

@@ -11,7 +11,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.service.util.OssObjectUrlUtils;
+import nju.seec.helper.util.OssObjectUrlUtils;
 import nju.seec.helper.util.OssUtils;
 import nju.seec.helper.vo.CourseFileVO;
 import org.springframework.data.domain.Page;

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

@@ -3,10 +3,7 @@ package nju.seec.helper.service.impl;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import nju.seec.helper.aspect.auth.LoginUser;
-import nju.seec.helper.dao.ChooseDAO;
-import nju.seec.helper.dao.QuizDAO;
-import nju.seec.helper.dao.QuizStudentAnswerDAO;
-import nju.seec.helper.dao.SlideDAO;
+import nju.seec.helper.dao.*;
 import nju.seec.helper.dto.quiz.QuizDTO;
 import nju.seec.helper.entity.Course;
 import nju.seec.helper.entity.Quiz;
@@ -39,15 +36,17 @@ public class QuizServiceImpl implements QuizService {
     private final QuizDAO quizDAO;
     private final QuizStudentAnswerDAO quizStudentAnswerDAO;
     private final ChooseDAO chooseDAO;
+    private final UserDAO userDAO;
 
     private final MessageService messageService;
 
     @Autowired
-    public QuizServiceImpl(SlideDAO slideDAO, QuizDAO quizDAO, QuizStudentAnswerDAO quizStudentAnswerDAO, ChooseDAO chooseDAO, MessageService messageService) {
+    public QuizServiceImpl(SlideDAO slideDAO, QuizDAO quizDAO, QuizStudentAnswerDAO quizStudentAnswerDAO, ChooseDAO chooseDAO, UserDAO userDAO, MessageService messageService) {
         this.slideDAO = slideDAO;
         this.quizDAO = quizDAO;
         this.quizStudentAnswerDAO = quizStudentAnswerDAO;
         this.chooseDAO = chooseDAO;
+        this.userDAO = userDAO;
         this.messageService = messageService;
     }
 
@@ -159,6 +158,12 @@ public class QuizServiceImpl implements QuizService {
         return quizDAO.findBySlideAndStateNotInAndNameContains(slideDAO.findSlideById(slideId), STATES_NOT_GET_BY_STUDENT_SET, key, pageable).map(QuizVO::new);
     }
 
+    @Transactional(readOnly = true)
+    @Override
+    public Page<QuizVO> teacherGetQuizzesByState(LoginUser user, QuizState state, String key, Pageable pageable) {
+        return quizDAO.findByTeacherAndStateAndNameContains(userDAO.findUserById(user.getId()), state, key, pageable).map(QuizVO::new);
+    }
+
     @Transactional(readOnly = true)
     @Override
     public Page<QuizVO> studentGetQuizzesByState(LoginUser user, QuizState state, String key, Pageable pageable) {

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

@@ -13,8 +13,8 @@ import nju.seec.helper.enums.ExceptionType;
 import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.service.QuestionService;
 import nju.seec.helper.service.QuizStudentAnswerService;
-import nju.seec.helper.vo.QuizStudentAnswerStatisticVO;
 import nju.seec.helper.vo.question.BaseQuestionVO;
+import nju.seec.helper.vo.quiz.QuizStudentAnswerStatisticVO;
 import nju.seec.helper.vo.quiz.QuizStudentAnswerVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -95,8 +95,8 @@ public class QuizStudentAnswerServiceImpl implements QuizStudentAnswerService {
 
     @Transactional(readOnly = true)
     @Override
-    public Page<QuizStudentAnswerVO> getQuizStudentAnswers(LoginUser user, Long quizId, Pageable pageable) {
-        return quizStudentAnswerDAO.findByQuiz(quizDAO.findQuizById(quizId), pageable).map(quizStudentAnswer -> new QuizStudentAnswerVO(quizStudentAnswer, questionService.getQuestionsByQuiz(quizDAO.findQuizById(quizId))));
+    public Page<QuizStudentAnswerVO> getQuizStudentAnswers(LoginUser user, Long quizId, String key, Pageable pageable) {
+        return quizStudentAnswerDAO.findByQuizIdAndKey(quizId, key, pageable).map(quizStudentAnswer -> new QuizStudentAnswerVO(quizStudentAnswer, questionService.getQuestionsByQuiz(quizDAO.findQuizById(quizId))));
     }
 
     @Transactional(readOnly = true)

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

@@ -21,7 +21,7 @@ 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.service.util.OssObjectUrlUtils;
+import nju.seec.helper.util.OssObjectUrlUtils;
 import nju.seec.helper.util.OssUtils;
 import nju.seec.helper.util.file.FileInfo;
 import nju.seec.helper.util.file.FileUtils;

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

@@ -3,7 +3,6 @@ package nju.seec.helper.util;
 import lombok.SneakyThrows;
 import lombok.experimental.UtilityClass;
 import org.apache.commons.codec.binary.Hex;
-import org.springframework.util.DigestUtils;
 
 import java.security.MessageDigest;
 
@@ -21,6 +20,6 @@ public class EncryptUtils {
     public String encode(String string) {
         MessageDigest messageDigest = MessageDigest.getInstance(ALGORITHM);
         byte[] hash = messageDigest.digest(string.getBytes());
-        return DigestUtils.md5DigestAsHex(Hex.encodeHexString(hash).getBytes());
+        return Hex.encodeHexString(hash);
     }
 }

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

@@ -0,0 +1,33 @@
+package nju.seec.helper.util;
+
+import nju.seec.helper.util.Consts;
+import nju.seec.helper.util.OssUtils;
+import nju.seec.helper.util.RedisCacheUtils;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @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 RedisCacheUtils cacheUtils;
+    private final OssUtils ossUtils;
+
+    public OssObjectUrlUtils(RedisCacheUtils cacheUtils, OssUtils ossUtils) {
+        this.cacheUtils = cacheUtils;
+        this.ossUtils = ossUtils;
+    }
+
+    public String getUrl(String objectName) {
+        String url = 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, OSS_OBJECT_URL_LIVING_SECONDS, TimeUnit.SECONDS);
+        }
+        return url;
+    }
+}

+ 18 - 0
src/main/java/nju/seec/helper/vo/quiz/QuizStudentAnswerStatisticVO.java

@@ -0,0 +1,18 @@
+package nju.seec.helper.vo.quiz;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+/**
+ * @author cst
+ */
+@AllArgsConstructor(staticName = "of")
+@Data
+public class QuizStudentAnswerStatisticVO {
+    private Long quizId;
+    private Map<BigDecimal, Integer> scores;
+    private Map<Integer, Integer> submitNums;
+}

+ 15 - 0
src/main/resources/ehcache.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<eh:config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:eh='http://www.ehcache.org/v3'
+           xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.3.xsd">
+    <eh:persistence directory="${java.io.tmpdir}/helper-cache-data"/>
+    <eh:cache-template name="default">
+        <eh:expiry>
+            <eh:ttl unit="seconds">600</eh:ttl>
+        </eh:expiry>
+        <eh:resources>            <!--堆内内存可以放2000个条目,超出部分堆外100MB-->
+            <eh:heap unit="entries">2000</eh:heap>
+            <eh:offheap unit="MB">100</eh:offheap>
+        </eh:resources>
+    </eh:cache-template>
+</eh:config>
+

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

@@ -0,0 +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