소스 검색

Merge branch 'iterator4'

ChenSiTong 6 년 전
부모
커밋
6deb703028
100개의 변경된 파일1626개의 추가작업 그리고 1099개의 파일을 삭제
  1. 0 1
      .gitignore
  2. 37 2
      pom.xml
  3. 19 20
      src/main/java/nju/seec/helper/api/BokApi.java
  4. 1 1
      src/main/java/nju/seec/helper/aspect/auth/Auth.java
  5. 2 3
      src/main/java/nju/seec/helper/aspect/auth/AuthAspect.java
  6. 2 3
      src/main/java/nju/seec/helper/aspect/auth/LoginUser.java
  7. 1 1
      src/main/java/nju/seec/helper/config/PageableConfig.java
  8. 0 14
      src/main/java/nju/seec/helper/config/properties/MailProperties.java
  9. 0 16
      src/main/java/nju/seec/helper/config/properties/OssProperties.java
  10. 0 16
      src/main/java/nju/seec/helper/config/properties/SmsProperties.java
  11. 88 0
      src/main/java/nju/seec/helper/controller/AdminController.java
  12. 0 6
      src/main/java/nju/seec/helper/controller/CodeController.java
  13. 5 5
      src/main/java/nju/seec/helper/controller/CommentController.java
  14. 2 1
      src/main/java/nju/seec/helper/controller/ControllerAdvice.java
  15. 24 10
      src/main/java/nju/seec/helper/controller/CourseController.java
  16. 16 2
      src/main/java/nju/seec/helper/controller/CourseFileController.java
  17. 16 2
      src/main/java/nju/seec/helper/controller/MessageController.java
  18. 28 2
      src/main/java/nju/seec/helper/controller/NoticeController.java
  19. 22 4
      src/main/java/nju/seec/helper/controller/QuestionController.java
  20. 19 14
      src/main/java/nju/seec/helper/controller/QuizController.java
  21. 4 2
      src/main/java/nju/seec/helper/controller/ReplyController.java
  22. 2 2
      src/main/java/nju/seec/helper/controller/SlideController.java
  23. 22 7
      src/main/java/nju/seec/helper/controller/UserController.java
  24. 22 0
      src/main/java/nju/seec/helper/dao/AdminDAO.java
  25. 29 2
      src/main/java/nju/seec/helper/dao/CommentDAO.java
  26. 55 6
      src/main/java/nju/seec/helper/dao/CourseDAO.java
  27. 2 2
      src/main/java/nju/seec/helper/dao/CourseFileDAO.java
  28. 9 0
      src/main/java/nju/seec/helper/dao/MessageDAO.java
  29. 25 2
      src/main/java/nju/seec/helper/dao/NoticeDAO.java
  30. 49 16
      src/main/java/nju/seec/helper/dao/QuizDAO.java
  31. 9 8
      src/main/java/nju/seec/helper/dao/QuizStudentAnswerDAO.java
  32. 2 2
      src/main/java/nju/seec/helper/dao/ReplyDAO.java
  33. 41 5
      src/main/java/nju/seec/helper/dao/SlideDAO.java
  34. 4 3
      src/main/java/nju/seec/helper/dao/UserDAO.java
  35. 2 4
      src/main/java/nju/seec/helper/dto/common/LoginDTO.java
  36. 1 1
      src/main/java/nju/seec/helper/dto/quiz/QuizDTO.java
  37. 1 1
      src/main/java/nju/seec/helper/dto/slide/SlideDTO.java
  38. 1 1
      src/main/java/nju/seec/helper/dto/slide/SlideStateDTO.java
  39. 5 0
      src/main/java/nju/seec/helper/dto/user/UserDTO.java
  40. 2 2
      src/main/java/nju/seec/helper/dto/validator/schoolemail/SchoolEmail.java
  41. 23 0
      src/main/java/nju/seec/helper/entity/Admin.java
  42. 1 2
      src/main/java/nju/seec/helper/entity/Choose.java
  43. 6 6
      src/main/java/nju/seec/helper/entity/Comment.java
  44. 9 6
      src/main/java/nju/seec/helper/entity/Course.java
  45. 7 5
      src/main/java/nju/seec/helper/entity/Message.java
  46. 1 1
      src/main/java/nju/seec/helper/entity/Notice.java
  47. 1 2
      src/main/java/nju/seec/helper/entity/QuestionRecord.java
  48. 13 12
      src/main/java/nju/seec/helper/entity/Quiz.java
  49. 9 8
      src/main/java/nju/seec/helper/entity/QuizStudentAnswer.java
  50. 10 8
      src/main/java/nju/seec/helper/entity/Slide.java
  51. 12 6
      src/main/java/nju/seec/helper/entity/User.java
  52. 0 23
      src/main/java/nju/seec/helper/entity/converter/ListConverter.java
  53. 0 23
      src/main/java/nju/seec/helper/entity/converter/MapConverter.java
  54. 5 0
      src/main/java/nju/seec/helper/entity/package-info.java
  55. 1 10
      src/main/java/nju/seec/helper/enums/ExceptionType.java
  56. 1 1
      src/main/java/nju/seec/helper/enums/MessageType.java
  57. 9 7
      src/main/java/nju/seec/helper/enums/QuestionType.java
  58. 23 0
      src/main/java/nju/seec/helper/enums/QuizState.java
  59. 19 0
      src/main/java/nju/seec/helper/enums/QuizType.java
  60. 1 1
      src/main/java/nju/seec/helper/enums/SlideState.java
  61. 8 0
      src/main/java/nju/seec/helper/enums/UserState.java
  62. 9 0
      src/main/java/nju/seec/helper/enums/UserType.java
  63. 2 2
      src/main/java/nju/seec/helper/exception/HelperException.java
  64. 58 0
      src/main/java/nju/seec/helper/service/AdminService.java
  65. 46 0
      src/main/java/nju/seec/helper/service/AuthService.java
  66. 5 3
      src/main/java/nju/seec/helper/service/CommentService.java
  67. 4 3
      src/main/java/nju/seec/helper/service/CourseFileService.java
  68. 15 13
      src/main/java/nju/seec/helper/service/CourseService.java
  69. 13 5
      src/main/java/nju/seec/helper/service/MessageService.java
  70. 4 3
      src/main/java/nju/seec/helper/service/NoticeService.java
  71. 1 1
      src/main/java/nju/seec/helper/service/QuestionService.java
  72. 5 82
      src/main/java/nju/seec/helper/service/QuizService.java
  73. 13 3
      src/main/java/nju/seec/helper/service/QuizStudentAnswerService.java
  74. 3 2
      src/main/java/nju/seec/helper/service/ReplyService.java
  75. 3 2
      src/main/java/nju/seec/helper/service/SlideService.java
  76. 1 1
      src/main/java/nju/seec/helper/service/UserService.java
  77. 86 0
      src/main/java/nju/seec/helper/service/impl/AdminServiceImpl.java
  78. 59 41
      src/main/java/nju/seec/helper/service/impl/CommentServiceImpl.java
  79. 52 33
      src/main/java/nju/seec/helper/service/impl/CourseFileServiceImpl.java
  80. 69 98
      src/main/java/nju/seec/helper/service/impl/CourseServiceImpl.java
  81. 10 4
      src/main/java/nju/seec/helper/service/impl/MessageServiceImpl.java
  82. 43 21
      src/main/java/nju/seec/helper/service/impl/NoticeServiceImpl.java
  83. 20 22
      src/main/java/nju/seec/helper/service/impl/QuestionServiceImpl.java
  84. 101 111
      src/main/java/nju/seec/helper/service/impl/QuizServiceImpl.java
  85. 26 7
      src/main/java/nju/seec/helper/service/impl/QuizStudentAnswerServiceImpl.java
  86. 37 12
      src/main/java/nju/seec/helper/service/impl/ReplyServiceImpl.java
  87. 104 136
      src/main/java/nju/seec/helper/service/impl/SlideServiceImpl.java
  88. 13 21
      src/main/java/nju/seec/helper/service/impl/UserServiceImpl.java
  89. 0 18
      src/main/java/nju/seec/helper/service/util/AuthUtils.java
  90. 33 0
      src/main/java/nju/seec/helper/service/util/OssObjectUrlUtils.java
  91. 4 5
      src/main/java/nju/seec/helper/util/Consts.java
  92. 9 6
      src/main/java/nju/seec/helper/util/MailUtils.java
  93. 16 14
      src/main/java/nju/seec/helper/util/OssUtils.java
  94. 7 8
      src/main/java/nju/seec/helper/util/RedisCacheUtils.java
  95. 12 10
      src/main/java/nju/seec/helper/util/SmsUtils.java
  96. 1 1
      src/main/java/nju/seec/helper/util/StringUtils.java
  97. 0 53
      src/main/java/nju/seec/helper/util/enums/QuizState.java
  98. 0 39
      src/main/java/nju/seec/helper/util/enums/QuizType.java
  99. 0 9
      src/main/java/nju/seec/helper/util/enums/UserType.java
  100. 14 0
      src/main/java/nju/seec/helper/util/file/FileInfo.java

+ 0 - 1
.gitignore

@@ -34,4 +34,3 @@ build/
 node_modules/
 
 .DS_Store
-config/

+ 37 - 2
pom.xml

@@ -10,17 +10,20 @@
     </parent>
     <groupId>cn.seecoder</groupId>
     <artifactId>helper-backend</artifactId>
-    <version>3.0.0</version>
+    <version>4.0.0</version>
     <packaging>jar</packaging>
     <name>helper</name>
     <description>backend</description>
 
     <properties>
         <java.version>1.8</java.version>
-        <swagger.version>2.8.0</swagger.version>
     </properties>
 
     <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-configuration-processor</artifactId>
@@ -78,6 +81,16 @@
             <artifactId>pdfbox</artifactId>
             <version>2.0.18</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>4.1.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-scratchpad</artifactId>
+            <version>4.1.2</version>
+        </dependency>
         <dependency>
             <groupId>com.aliyun.oss</groupId>
             <artifactId>aliyun-sdk-oss</artifactId>
@@ -98,6 +111,12 @@
             <artifactId>gson</artifactId>
             <version>2.8.6</version>
         </dependency>
+
+        <dependency>
+            <groupId>com.vladmihalcea</groupId>
+            <artifactId>hibernate-types-52</artifactId>
+            <version>2.9.5</version>
+        </dependency>
     </dependencies>
 
     <build>
@@ -106,6 +125,22 @@
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
+            <plugin>
+                <groupId>com.github.apiggs</groupId>
+                <artifactId>apiggs-maven-plugin</artifactId>
+                <version>1.6</version>
+                <executions>
+                    <execution>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>apiggs</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <!-- options in there -->
+                </configuration>
+            </plugin>
         </plugins>
     </build>
     <distributionManagement>

+ 19 - 20
src/main/java/nju/seec/helper/util/BokUtil.java → src/main/java/nju/seec/helper/api/BokApi.java

@@ -1,4 +1,4 @@
-package nju.seec.helper.util;
+package nju.seec.helper.api;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.collect.ImmutableMap;
@@ -9,9 +9,11 @@ import lombok.extern.slf4j.Slf4j;
 import nju.seec.helper.dto.question.BaseQuestionDTO;
 import nju.seec.helper.dto.question.ChoiceQuestionDTO;
 import nju.seec.helper.dto.question.TrueOrFalseQuestionDTO;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.enums.QuestionType;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.enums.QuestionType;
+import nju.seec.helper.exception.HelperException;
+import nju.seec.helper.util.GuavaCacheUtil;
+import nju.seec.helper.util.RestRequestUtil;
 import nju.seec.helper.vo.question.BokQuestion;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.domain.Page;
@@ -32,15 +34,17 @@ import java.util.stream.Collectors;
  */
 @Slf4j
 @Component
-public class BokUtil {
+public class BokApi {
     private final RestRequestUtil restRequestUtil;
     private final GuavaCacheUtil cacheUtils;
-    @Value("${bok.searchUrl}")
-    private String searchUrl;
     @Value("${bok.tqUrl}")
     private String tqUrl;
+    @Value("${bok.tqStemSearchUrl}")
+    private String tqStemSearchUrl;
+    @Value("${bok.tqIdSearchUrl}")
+    private String tqIdSearchUrl;
 
-    public BokUtil(RestRequestUtil restRequestUtil, GuavaCacheUtil cacheUtils) {
+    public BokApi(RestRequestUtil restRequestUtil, GuavaCacheUtil cacheUtils) {
         this.restRequestUtil = restRequestUtil;
         this.cacheUtils = cacheUtils;
     }
@@ -51,14 +55,10 @@ public class BokUtil {
                 "page", String.valueOf(1 + pageable.getPageNumber()),
                 "size", String.valueOf(pageable.getPageSize())
         );
-        BokSearchResult result = restRequestUtil.sendGetRequest(
-                searchUrl + "findByStemLike?content={content}&size={size}&page={page}", BokSearchResult.class, urlParams);
+        BokSearchResult result = restRequestUtil.sendGetRequest(tqStemSearchUrl, BokSearchResult.class, urlParams);
 
         List<BokQuestion> bokQuestions = result.getEmbedded().getQuestions();
-        cacheUtils.setAll(BOK_CACHE_NAME,
-                bokQuestions.parallelStream()
-                        .collect(Collectors.toMap(BokQuestion::getId, bokQuestion -> bokQuestion))
-        );
+        cacheUtils.setAll(BOK_CACHE_NAME, bokQuestions.parallelStream().collect(Collectors.toMap(BokQuestion::getId, bokQuestion -> bokQuestion)));
         return new PageImpl<>(bokQuestions, pageable, result.getPage().getTotalElements());
     }
 
@@ -77,11 +77,10 @@ public class BokUtil {
         if (!requestIds.isEmpty()) {
             Map<String, String> urlParams = ImmutableMap.of("id", requestIds.stream().reduce((a, b) -> a + "," + b).orElse(""));
 
-            BokSearchResult result = restRequestUtil.sendGetRequest(searchUrl + "findByIdIn?id={id}", BokSearchResult.class, urlParams);
+            BokSearchResult result = restRequestUtil.sendGetRequest(tqIdSearchUrl, BokSearchResult.class, urlParams);
             List<BokQuestion> bokQuestions = result.getEmbedded().getQuestions();
 
-            Map<String, BokQuestion> remoteBokQuestionsMap = bokQuestions.parallelStream()
-                    .collect(Collectors.toMap(BokQuestion::getId, bokQuestion -> bokQuestion));
+            Map<String, BokQuestion> remoteBokQuestionsMap = bokQuestions.parallelStream().collect(Collectors.toMap(BokQuestion::getId, bokQuestion -> bokQuestion));
 
             bokQuestionsMap.putAll(remoteBokQuestionsMap);
             cacheUtils.setAll(BOK_CACHE_NAME, (Map) remoteBokQuestionsMap);
@@ -97,7 +96,7 @@ public class BokUtil {
         if (cachedBokQuestion instanceof BokQuestion) {
             return (BokQuestion) cachedBokQuestion;
         }
-        BokQuestion bokQuestion = restRequestUtil.sendGetRequest(tqUrl + questionId, BokQuestion.class, Collections.emptyMap());
+        BokQuestion bokQuestion = restRequestUtil.sendGetRequest(tqUrl + "/" + questionId, BokQuestion.class, Collections.emptyMap());
         cacheUtils.set(BOK_CACHE_NAME, bokQuestion.getId(), bokQuestion);
         return bokQuestion;
     }
@@ -112,12 +111,12 @@ public class BokUtil {
     public BokQuestion modifyQuestion(String questionId, BaseQuestionDTO baseQuestionDTO) {
         BokQuestion bokQuestion = getQuestion(baseQuestionDTO);
         bokQuestion.setId(questionId);
-        restRequestUtil.sendPutRequest(tqUrl + questionId, bokQuestion);
+        restRequestUtil.sendPutRequest(tqUrl + "/" + questionId, bokQuestion);
         return bokFindById(questionId);
     }
 
     public void deleteQuestion(String questionId) {
-        restRequestUtil.sendDeleteRequest(tqUrl + questionId);
+        restRequestUtil.sendDeleteRequest(tqUrl + "/" + questionId);
     }
 
     private BokQuestion getQuestion(BaseQuestionDTO baseQuestionDTO) {

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

@@ -1,6 +1,6 @@
 package nju.seec.helper.aspect.auth;
 
-import nju.seec.helper.util.enums.UserType;
+import nju.seec.helper.enums.UserType;
 
 import java.lang.annotation.*;
 

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

@@ -1,9 +1,8 @@
 package nju.seec.helper.aspect.auth;
 
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.util.Consts;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Before;

+ 2 - 3
src/main/java/nju/seec/helper/dto/user/LoginUser.java → src/main/java/nju/seec/helper/aspect/auth/LoginUser.java

@@ -1,8 +1,8 @@
-package nju.seec.helper.dto.user;
+package nju.seec.helper.aspect.auth;
 
 import lombok.Data;
 import lombok.experimental.Accessors;
-import nju.seec.helper.util.enums.UserType;
+import nju.seec.helper.enums.UserType;
 
 /**
  * @author cst
@@ -11,6 +11,5 @@ import nju.seec.helper.util.enums.UserType;
 @Accessors(chain = true)
 public class LoginUser {
     private Long id;
-    private String name;
     private UserType type;
 }

+ 1 - 1
src/main/java/nju/seec/helper/config/PageableConfig.java

@@ -10,7 +10,7 @@ import org.springframework.data.web.config.PageableHandlerMethodArgumentResolver
  * @Author SheentXu
  * @Date 2019/12/24
  * @Version 1.0
- * @Description //TODO
+ * @Description
  **/
 @Configuration
 public class PageableConfig {

+ 0 - 14
src/main/java/nju/seec/helper/config/properties/MailProperties.java

@@ -1,14 +0,0 @@
-package nju.seec.helper.config.properties;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-/**
- * @author cst
- */
-@Data
-@ConfigurationProperties("helper.mail")
-public class MailProperties {
-    private String from;
-    private String subject;
-}

+ 0 - 16
src/main/java/nju/seec/helper/config/properties/OssProperties.java

@@ -1,16 +0,0 @@
-package nju.seec.helper.config.properties;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-/**
- * @author cst
- */
-@Data
-@ConfigurationProperties("aliyun.oss")
-public class OssProperties {
-    private String endpoint;
-    private String accessKeyId;
-    private String accessKeySecret;
-    private String bucketName;
-}

+ 0 - 16
src/main/java/nju/seec/helper/config/properties/SmsProperties.java

@@ -1,16 +0,0 @@
-package nju.seec.helper.config.properties;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-/**
- * @author cst
- */
-@Data
-@ConfigurationProperties("aliyun.sms")
-public class SmsProperties {
-    private String accessKeyId;
-    private String accessSecret;
-    private String signName;
-    private String templateCode;
-}

+ 88 - 0
src/main/java/nju/seec/helper/controller/AdminController.java

@@ -0,0 +1,88 @@
+package nju.seec.helper.controller;
+
+import nju.seec.helper.aspect.auth.Auth;
+import nju.seec.helper.aspect.auth.LoginUser;
+import nju.seec.helper.controller.response.PageResponse;
+import nju.seec.helper.dto.common.LoginDTO;
+import nju.seec.helper.enums.UserState;
+import nju.seec.helper.enums.UserType;
+import nju.seec.helper.service.AdminService;
+import nju.seec.helper.util.Consts;
+import nju.seec.helper.vo.AdminVO;
+import nju.seec.helper.vo.UserVO;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpSession;
+
+/**
+ * 管理员
+ *
+ * @author cst
+ */
+@RestController
+@RequestMapping("/api/admin")
+public class AdminController {
+    private final AdminService adminService;
+
+    public AdminController(AdminService adminService) {
+        this.adminService = adminService;
+    }
+
+    /**
+     * 管理员登录
+     */
+    @PostMapping("/login")
+    public AdminVO login(@Validated @RequestBody LoginDTO loginDTO, HttpSession session) {
+        AdminVO adminVO = adminService.getAdminByUsernameAndPassword(loginDTO.getUsername(), loginDTO.getPassword());
+
+        LoginUser user = new LoginUser()
+                .setId(adminVO.getId())
+                .setType(UserType.ADMIN);
+
+        session.setAttribute(Consts.SESSION_USER_NAME, user);
+        // 30分钟有效期
+        session.setMaxInactiveInterval(30 * 60);
+        return adminVO;
+    }
+
+    /**
+     * 获取注册教师列表
+     */
+    @Auth(roles = UserType.ADMIN, message = "获取注册教师列表")
+    @GetMapping("/user")
+    public PageResponse<UserVO> getUsers(LoginUser admin, UserType type, UserState state,
+                                         @RequestParam(required = false, defaultValue = "") String key,
+                                         @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
+        return PageResponse.of(adminService.getUsers(type, state, key, pageable));
+    }
+
+    /**
+     * 审核通过用户
+     */
+    @Auth(roles = UserType.ADMIN, message = "审核通过用户")
+    @PutMapping("/user/{userId}/pass")
+    public void passUser(LoginUser admin, @PathVariable Long userId) {
+        adminService.passUser(admin, userId);
+    }
+
+    /**
+     * 封禁用户
+     */
+    @Auth(roles = UserType.ADMIN, message = "封禁用户")
+    @PutMapping("/user/{userId}/lock")
+    public void lockUser(LoginUser admin, @PathVariable Long userId) {
+        adminService.lockUser(admin, userId);
+    }
+
+    /**
+     * 解封用户
+     */
+    @Auth(roles = UserType.ADMIN, message = "解封用户")
+    @PutMapping("/user/{userId}/unlock")
+    public void unlockUser(LoginUser admin, @PathVariable Long userId) {
+        adminService.unlockUser(admin, userId);
+    }
+}

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

@@ -27,9 +27,6 @@ public class CodeController {
 
     /**
      * 发送邮箱验证码
-     *
-     * @param email
-     * @return
      */
     @PostMapping("/email")
     public void sendEmailCode(@NotBlank(message = "邮箱不能为空") @SchoolEmail String email) {
@@ -38,9 +35,6 @@ public class CodeController {
 
     /**
      * 发送手机验证码
-     *
-     * @param phone
-     * @return
      */
     @PostMapping("/phone")
     public void sendPhoneCode(@NotBlank(message = "手机号不能为空") @Phone String phone) {

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

@@ -1,12 +1,12 @@
 package nju.seec.helper.controller;
 
 import nju.seec.helper.aspect.auth.Auth;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.comment.CommentDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.enums.UserType;
 import nju.seec.helper.service.CommentService;
 import nju.seec.helper.util.JsonUtils;
-import nju.seec.helper.util.enums.UserType;
 import nju.seec.helper.vo.CommentVO;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.web.PageableDefault;
@@ -81,11 +81,11 @@ public class CommentController {
     }
 
     /**
-     * 取得某评论
+     * 取得某评论
      */
     @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取评论")
     @GetMapping("/{commentId}")
-    public CommentVO getOneComment(@PathVariable Long commentId) {
-        return commentService.getOneComment(commentId);
+    public CommentVO getOneComment(LoginUser user, @PathVariable Long commentId) {
+        return commentService.getOneComment(user, commentId);
     }
 }

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

@@ -2,7 +2,7 @@ package nju.seec.helper.controller;
 
 import lombok.extern.slf4j.Slf4j;
 import nju.seec.helper.controller.response.ErrorResponse;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.exception.HelperException;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.validation.BindException;
@@ -28,6 +28,7 @@ public class ControllerAdvice {
 
     @ExceptionHandler(MethodArgumentTypeMismatchException.class)
     public ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
+        log.error(e.getParameter().getParameterName(), e);
         return ResponseEntity.badRequest().body(ErrorResponse.of("路径参数错误"));
     }
 

+ 24 - 10
src/main/java/nju/seec/helper/controller/CourseController.java

@@ -1,14 +1,14 @@
 package nju.seec.helper.controller;
 
 import nju.seec.helper.aspect.auth.Auth;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.course.ChooseDTO;
 import nju.seec.helper.dto.course.CourseDTO;
 import nju.seec.helper.dto.groups.Create;
 import nju.seec.helper.dto.groups.Modify;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.enums.UserType;
 import nju.seec.helper.service.CourseService;
-import nju.seec.helper.util.enums.UserType;
 import nju.seec.helper.vo.CourseVO;
 import nju.seec.helper.vo.UserVO;
 import org.springframework.data.domain.Pageable;
@@ -85,21 +85,32 @@ public class CourseController {
     /**
      * 获取课程列表(无需登录)
      */
-    @GetMapping("/all")
+    @GetMapping
     public PageResponse<CourseVO> getCourses(@RequestParam(required = false, defaultValue = "") String key,
                                              @PageableDefault(Integer.MAX_VALUE) Pageable pageable) {
         return PageResponse.of(courseService.getCourses(key, pageable));
     }
 
     /**
-     * 获取课程列表
+     * 获取创建课程列表
      */
-    @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取课程列表")
-    @GetMapping
-    public PageResponse<CourseVO> getCourses(LoginUser user,
-                                             @RequestParam(required = false, defaultValue = "") String key,
-                                             @PageableDefault(Integer.MAX_VALUE) Pageable pageable) {
-        return PageResponse.of(courseService.getCourses(user, key, pageable));
+    @Auth(roles = UserType.TEACHER, message = "获取创建课程列表")
+    @GetMapping("/created")
+    public PageResponse<CourseVO> getCreatedCourses(LoginUser user,
+                                                    @RequestParam(required = false, defaultValue = "") String key,
+                                                    @PageableDefault(Integer.MAX_VALUE) Pageable pageable) {
+        return PageResponse.of(courseService.getCreatedCourses(user, key, pageable));
+    }
+
+    /**
+     * 获取选课列表
+     */
+    @Auth(roles = UserType.STUDENT, message = "获取选课列表")
+    @GetMapping("/chosen")
+    public PageResponse<CourseVO> getChosenCourses(LoginUser user,
+                                                   @RequestParam(required = false, defaultValue = "") String key,
+                                                   @PageableDefault(Integer.MAX_VALUE) Pageable pageable) {
+        return PageResponse.of(courseService.getChosenCourses(user, key, pageable));
     }
 
     /**
@@ -120,6 +131,9 @@ public class CourseController {
         return courseService.getOneCourse(courseId);
     }
 
+    /**
+     * 获取选课名单
+     */
     @Auth(roles = UserType.TEACHER, message = "获取选课名单")
     @GetMapping("/{courseId}/students")
     public PageResponse<UserVO> getChooseStudents(LoginUser user,

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

@@ -1,11 +1,11 @@
 package nju.seec.helper.controller;
 
 import nju.seec.helper.aspect.auth.Auth;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.course.CourseFileDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.enums.UserType;
 import nju.seec.helper.service.CourseFileService;
-import nju.seec.helper.util.enums.UserType;
 import nju.seec.helper.vo.CourseFileVO;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.web.PageableDefault;
@@ -13,6 +13,8 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 /**
+ * 课程附件
+ *
  * @author cst
  */
 @RestController
@@ -24,6 +26,9 @@ public class CourseFileController {
         this.courseFileService = courseFileService;
     }
 
+    /**
+     * 上传课程附件
+     */
     @Auth(roles = UserType.TEACHER, message = "上传课程附件")
     @PostMapping
     public void uploadCourseFile(LoginUser user,
@@ -31,6 +36,9 @@ public class CourseFileController {
         courseFileService.uploadCourseFile(user, courseFileDTO);
     }
 
+    /**
+     * 删除课程附件
+     */
     @Auth(roles = UserType.TEACHER, message = "删除课程附件")
     @DeleteMapping("/{courseFileId}")
     public void deleteCourseFile(LoginUser user,
@@ -38,6 +46,9 @@ public class CourseFileController {
         courseFileService.deleteCourseFile(user, courseFileId);
     }
 
+    /**
+     * 获取课程附件
+     */
     @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取课程附件")
     @GetMapping("/course/{courseId}")
     public PageResponse<CourseFileVO> getCourseFilesByCourse(LoginUser user,
@@ -47,6 +58,9 @@ public class CourseFileController {
         return PageResponse.of(courseFileService.getCourseFilesByCourse(user, courseId, key, pageable));
     }
 
+    /**
+     * 获取课程附件链接
+     */
     @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取课程附件链接")
     @GetMapping("/{courseFileId}/url")
     public String getOneCourseFile(LoginUser user,

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

@@ -1,18 +1,20 @@
 package nju.seec.helper.controller;
 
 import nju.seec.helper.aspect.auth.Auth;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.message.MessageDTO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.entity.Message;
+import nju.seec.helper.enums.UserType;
 import nju.seec.helper.service.MessageService;
-import nju.seec.helper.util.enums.UserType;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.web.PageableDefault;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 /**
+ * 消息
+ *
  * @author cst
  */
 @RestController
@@ -24,6 +26,9 @@ public class MessageController {
         this.messageService = messageService;
     }
 
+    /**
+     * 设置已读
+     */
     @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "设置已读")
     @PutMapping("/read")
     public void readMessages(LoginUser user,
@@ -31,6 +36,9 @@ public class MessageController {
         messageService.readMessages(user, messageDTO);
     }
 
+    /**
+     * 删除消息
+     */
     @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "删除消息")
     @PutMapping("/delete")
     public void deleteMessages(LoginUser user,
@@ -38,6 +46,9 @@ public class MessageController {
         messageService.deleteMessages(user, messageDTO);
     }
 
+    /**
+     * 获取未读消息
+     */
     @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取未读消息")
     @GetMapping("/unread")
     public PageResponse<Message> getUnreadMessages(LoginUser user,
@@ -46,6 +57,9 @@ public class MessageController {
         return PageResponse.of(messageService.getUnreadMessages(user, key, pageable));
     }
 
+    /**
+     * 获取已读消息
+     */
     @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取已读消息")
     @GetMapping("/read")
     public PageResponse<Message> getReadMessages(LoginUser user,

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

@@ -1,13 +1,13 @@
 package nju.seec.helper.controller;
 
 import nju.seec.helper.aspect.auth.Auth;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.groups.Create;
 import nju.seec.helper.dto.groups.Modify;
 import nju.seec.helper.dto.notice.NoticeDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.enums.UserType;
 import nju.seec.helper.service.NoticeService;
-import nju.seec.helper.util.enums.UserType;
 import nju.seec.helper.vo.NoticeVO;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.web.PageableDefault;
@@ -15,6 +15,8 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 /**
+ * 公告
+ *
  * @author cst
  */
 @RestController
@@ -26,6 +28,13 @@ public class NoticeController {
         this.noticeService = noticeService;
     }
 
+    /**
+     * 发布公告
+     *
+     * @param user
+     * @param noticeDTO
+     * @return
+     */
     @Auth(roles = UserType.TEACHER, message = "发布公告")
     @PostMapping
     public NoticeVO createNotice(LoginUser user,
@@ -33,6 +42,14 @@ public class NoticeController {
         return noticeService.createNotice(user, noticeDTO);
     }
 
+    /**
+     * 修改公告
+     *
+     * @param user
+     * @param noticeId
+     * @param noticeDTO
+     * @return
+     */
     @Auth(roles = UserType.TEACHER, message = "修改公告")
     @PutMapping("/{noticeId}")
     public NoticeVO modifyNotice(LoginUser user,
@@ -41,6 +58,9 @@ public class NoticeController {
         return noticeService.modifyNotice(user, noticeId, noticeDTO);
     }
 
+    /**
+     * 删除公告
+     */
     @Auth(roles = UserType.TEACHER, message = "删除公告")
     @DeleteMapping("/{noticeId}")
     public void deleteNotice(LoginUser user,
@@ -48,6 +68,9 @@ public class NoticeController {
         noticeService.deleteNotice(user, noticeId);
     }
 
+    /**
+     * 获取课程公告
+     */
     @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取课程公告")
     @GetMapping("/course/{courseId}")
     public PageResponse<NoticeVO> getNotices(LoginUser user,
@@ -57,6 +80,9 @@ public class NoticeController {
         return PageResponse.of(noticeService.getNoticesByCourse(user, courseId, key, pageable));
     }
 
+    /**
+     * 获取某一公告
+     */
     @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取公告")
     @GetMapping("/{noticeId}")
     public NoticeVO getOneNotice(LoginUser user,

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

@@ -1,13 +1,13 @@
 package nju.seec.helper.controller;
 
 import nju.seec.helper.aspect.auth.Auth;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.groups.Create;
 import nju.seec.helper.dto.groups.Modify;
 import nju.seec.helper.dto.question.BaseQuestionDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.enums.UserType;
 import nju.seec.helper.service.QuestionService;
-import nju.seec.helper.util.enums.UserType;
 import nju.seec.helper.vo.question.BaseQuestionVO;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.web.PageableDefault;
@@ -44,7 +44,7 @@ public class QuestionController {
     }
 
     /**
-     * 某一测试题目
+     * 获取某一测试题目
      */
     @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获得测试题目")
     @GetMapping("/quiz/{quizId}")
@@ -52,30 +52,45 @@ public class QuestionController {
         return questionService.getQuestionsByQuiz(user, quizId);
     }
 
+    /**
+     * 获取某一题目信息
+     */
     @Auth(roles = UserType.TEACHER, message = "获取题目信息")
     @GetMapping("/{questionId}")
     public BaseQuestionVO getOneQuestion(LoginUser user, @PathVariable String questionId) {
         return questionService.getOneQuestion(user, questionId);
     }
 
+    /**
+     * 创建题目
+     */
     @Auth(roles = UserType.TEACHER, message = "创建题目")
     @PostMapping
     public BaseQuestionVO createQuestion(LoginUser user, @Validated(Create.class) @RequestBody BaseQuestionDTO baseQuestionDTO) {
         return questionService.createQuestion(user, baseQuestionDTO);
     }
 
+    /**
+     * 修改题目
+     */
     @Auth(roles = UserType.TEACHER, message = "修改题目")
     @PutMapping("/{questionId}")
     public BaseQuestionVO modifyQuestion(LoginUser user, @PathVariable String questionId, @Validated(Modify.class) @RequestBody BaseQuestionDTO baseQuestionDTO) {
         return questionService.modifyQuestion(user, questionId, baseQuestionDTO);
     }
 
+    /**
+     * 删除题目
+     */
     @Auth(roles = UserType.TEACHER, message = "删除题目")
     @DeleteMapping("/{questionId}")
     public void deleteQuestion(LoginUser user, @PathVariable String questionId) {
         questionService.deleteQuestion(user, questionId);
     }
 
+    /**
+     * 获取创建题目
+     */
     @Auth(roles = UserType.TEACHER, message = "获取创建的题目")
     @GetMapping("/created")
     public PageResponse<BaseQuestionVO> getCreatedQuestions(LoginUser user,
@@ -83,9 +98,12 @@ public class QuestionController {
         return PageResponse.of(questionService.getCreatedQuestions(user, pageable));
     }
 
+    /**
+     * 获取创建题目ID
+     */
     @Auth(roles = UserType.TEACHER)
     @GetMapping("/created/ids")
-    public Set<String> getIdsOfCreatedQuestions(LoginUser user) {
+    public Set<String> getCreatedQuestionIds(LoginUser user) {
         return questionService.getIdsOfCreatedQuestions(user);
     }
 }

+ 19 - 14
src/main/java/nju/seec/helper/controller/QuizController.java

@@ -1,16 +1,17 @@
 package nju.seec.helper.controller;
 
 import nju.seec.helper.aspect.auth.Auth;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.groups.Create;
 import nju.seec.helper.dto.groups.Modify;
 import nju.seec.helper.dto.quiz.QuizDTO;
 import nju.seec.helper.dto.quiz.QuizStudentAnswerDTO;
-import nju.seec.helper.dto.user.LoginUser;
+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.util.enums.QuizState;
-import nju.seec.helper.util.enums.UserType;
+import nju.seec.helper.vo.QuizStudentAnswerStatisticVO;
 import nju.seec.helper.vo.quiz.QuizStudentAnswerVO;
 import nju.seec.helper.vo.quiz.QuizVO;
 import org.springframework.data.domain.Pageable;
@@ -109,6 +110,9 @@ public class QuizController {
         quizService.deleteQuiz(user, Long.valueOf(quizId));
     }
 
+    /**
+     * 提交作答
+     */
     @Auth(roles = {UserType.STUDENT}, message = "提交作答")
     @PostMapping("/{quizId}/student-answer")
     public QuizStudentAnswerVO submitQuizStudentAnswer(LoginUser user, @PathVariable Long quizId, @RequestBody QuizStudentAnswerDTO quizStudentAnswerDTO) {
@@ -116,28 +120,29 @@ public class QuizController {
     }
 
     /**
-     * 老师获取某一测试学生的作答列表
-     *
-     * @param user
-     * @param quizId
-     * @return
+     * 获取测试作答列表
      */
     @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.teacherGetQuizStudentAnswers(user, quizId, pageable));
+        return PageResponse.of(quizStudentAnswerService.getQuizStudentAnswers(user, quizId, pageable));
     }
 
     /**
      * 学生获取某一测试自己的作答
-     *
-     * @param user
-     * @param quizId
-     * @return
      */
     @Auth(roles = {UserType.STUDENT}, message = "获取作答")
     @GetMapping("/{quizId}/student-answer")
     public QuizStudentAnswerVO getOneQuizStudentAnswer(LoginUser user, @PathVariable Long quizId) {
-        return quizStudentAnswerService.studentGetSelfQuizStudentAnswer(user, quizId);
+        return quizStudentAnswerService.getQuizStudentAnswer(user, quizId);
+    }
+
+    /**
+     * 获取测试作答提交的统计信息
+     */
+    @Auth(roles = {UserType.TEACHER}, message = "获取测试统计消息")
+    @GetMapping("/{quizId}/statistic")
+    public QuizStudentAnswerStatisticVO getQuizStatistic(LoginUser user, @PathVariable Long quizId) {
+        return quizStudentAnswerService.getQuizStudentAnswerStatistic(user, quizId);
     }
 }

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

@@ -1,15 +1,17 @@
 package nju.seec.helper.controller;
 
 import nju.seec.helper.aspect.auth.Auth;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.reply.ReplyDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.enums.UserType;
 import nju.seec.helper.service.ReplyService;
-import nju.seec.helper.util.enums.UserType;
 import nju.seec.helper.vo.ReplyVO;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 /**
+ * 回复
+ *
  * @author cst
  */
 @RestController

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

@@ -1,15 +1,15 @@
 package nju.seec.helper.controller;
 
 import nju.seec.helper.aspect.auth.Auth;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.groups.Create;
 import nju.seec.helper.dto.groups.Modify;
 import nju.seec.helper.dto.slide.SlideDTO;
 import nju.seec.helper.dto.slide.SlideFileDTO;
 import nju.seec.helper.dto.slide.SlideStateDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.enums.UserType;
 import nju.seec.helper.service.SlideService;
-import nju.seec.helper.util.enums.UserType;
 import nju.seec.helper.vo.SlideVO;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.web.PageableDefault;

+ 22 - 7
src/main/java/nju/seec/helper/controller/UserController.java

@@ -1,20 +1,26 @@
 package nju.seec.helper.controller;
 
+import com.google.common.collect.ImmutableMap;
 import nju.seec.helper.aspect.auth.Auth;
+import nju.seec.helper.aspect.auth.LoginUser;
+import nju.seec.helper.dto.common.LoginDTO;
 import nju.seec.helper.dto.groups.Create;
 import nju.seec.helper.dto.groups.Modify;
-import nju.seec.helper.dto.user.LoginDTO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.dto.user.ResetPasswordDTO;
 import nju.seec.helper.dto.user.UserDTO;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.enums.UserState;
+import nju.seec.helper.enums.UserType;
+import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.service.UserService;
 import nju.seec.helper.util.Consts;
-import nju.seec.helper.util.enums.UserType;
 import nju.seec.helper.vo.UserVO;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpSession;
+import java.util.Map;
 
 /**
  * 用户
@@ -24,6 +30,9 @@ import javax.servlet.http.HttpSession;
 @RestController
 @RequestMapping("/api/user")
 public class UserController {
+    @Value("${helper.user-timeout-seconds}")
+    private int userTimeoutSeconds;
+
     private final UserService userService;
 
     public UserController(UserService userService) {
@@ -38,6 +47,11 @@ public class UserController {
         return userService.createUser(userDTO);
     }
 
+    private static final Map<UserState, String> STATE_MESSAGE_MAP = ImmutableMap.of(
+            UserState.CHECKING, "帐号审核中",
+            UserState.LOCKED, "帐号封禁中"
+    );
+
     /**
      * 邮箱登录
      */
@@ -46,14 +60,16 @@ public class UserController {
                                HttpSession session) {
         UserVO userVO = userService.getUserByEmailAndPassword(loginDTO.getUsername(), loginDTO.getPassword());
 
+        if (userVO.getState() != UserState.NORMAL) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, STATE_MESSAGE_MAP.get(userVO.getState()));
+        }
+
         LoginUser user = new LoginUser()
                 .setId(userVO.getId())
-                .setName(userVO.getName())
                 .setType(userVO.getType());
 
         session.setAttribute(Consts.SESSION_USER_NAME, user);
-        // 3小时有效期
-        session.setMaxInactiveInterval(3 * 60 * 60);
+        session.setMaxInactiveInterval(userTimeoutSeconds);
         return userVO;
     }
 
@@ -77,7 +93,6 @@ public class UserController {
         UserVO userVO = userService.modifyUser(user, userDTO);
         user = ((LoginUser) session.getAttribute(Consts.SESSION_USER_NAME))
                 .setId(userVO.getId())
-                .setName(userVO.getName())
                 .setType(userVO.getType());
         session.setAttribute(Consts.SESSION_USER_NAME, user);
         return userVO;

+ 22 - 0
src/main/java/nju/seec/helper/dao/AdminDAO.java

@@ -0,0 +1,22 @@
+package nju.seec.helper.dao;
+
+import nju.seec.helper.entity.Admin;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+/**
+ * @author cst
+ */
+@Repository
+public interface AdminDAO extends JpaRepository<Admin, Long> {
+    /**
+     * 用户名密码查询
+     *
+     * @param username
+     * @param password
+     * @return
+     */
+    Optional<Admin> findByUsernameAndPassword(String username, String password);
+}

+ 29 - 2
src/main/java/nju/seec/helper/dao/CommentDAO.java

@@ -1,12 +1,16 @@
 package nju.seec.helper.dao;
 
 import nju.seec.helper.entity.Comment;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.exception.HelperException;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.stereotype.Repository;
 
+import java.util.List;
+
 /**
  * @author cst
  */
@@ -22,4 +26,27 @@ public interface CommentDAO extends JpaRepository<Comment, Long>, JpaSpecificati
         return this.findById(id)
                 .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到评论"));
     }
+
+    /**
+     * 根据课件ID、页码、置顶指数统计
+     *
+     * @param slideId
+     * @param pageNumber
+     * @param topNumber
+     * @return
+     */
+    long countBySlideIdAndPageNumberAndTopNumberNot(Long slideId, Integer pageNumber, Integer topNumber);
+
+    List<Comment> findBySlideIdAndPageNumberAndTopNumberGreaterThan(Long slideId, Integer pageNumber, Integer topNumber);
+
+    /**
+     * 根据课件ID、页码、是否展示获取
+     *
+     * @param slideId
+     * @param pageNumber
+     * @param show
+     * @param pageable
+     * @return
+     */
+    Page<Comment> findBySlideIdAndPageNumberAndShow(Long slideId, Integer pageNumber, Boolean show, Pageable pageable);
 }

+ 55 - 6
src/main/java/nju/seec/helper/dao/CourseDAO.java

@@ -1,11 +1,19 @@
 package nju.seec.helper.dao;
 
 import nju.seec.helper.entity.Course;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.entity.User;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.exception.HelperException;
+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.Set;
 
 /**
  * @author cst
@@ -19,17 +27,58 @@ public interface CourseDAO extends JpaRepository<Course, Long>, JpaSpecification
      * @return
      */
     default Course findCourseById(Long id) {
-        return this.findById(id)
-                .filter(course -> course.getDeleteAt() == 0)
-                .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到课程"));
+        return this.findById(id).orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到课程"));
     }
 
     /**
-     * 基于课程选课码检索
+     * 课程ID、选课码检查
      *
      * @param courseId
      * @param code
      * @return
      */
     boolean existsByIdAndCode(Long courseId, String code);
+
+    /**
+     * 根据关键字检索
+     *
+     * @param key
+     * @param pageable
+     * @return
+     */
+    default Page<Course> findByKey(String key, Pageable pageable) {
+        String keyPattern = nju.seec.helper.util.StringUtils.keyPattern(key);
+
+        return StringUtils.hasText(key) ?
+                this.findAll((Specification<Course>) (root, query, cb) -> cb.or(cb.like(root.get("name"), keyPattern), cb.like(root.join("teacher", JoinType.LEFT).get("name"), keyPattern)), pageable) :
+                this.findAll(pageable);
+    }
+
+    /**
+     * 根据教师、关键字检索
+     *
+     * @param teacher
+     * @param name
+     * @param pageable
+     * @return
+     */
+    Page<Course> findByTeacherAndNameContains(User teacher, String name, Pageable pageable);
+
+    /**
+     * 根据ID、关键字检索
+     *
+     * @param courseIds
+     * @param key
+     * @param pageable
+     * @return
+     */
+    default Page<Course> findByIdsAndKey(Set<Long> courseIds, String key, Pageable pageable) {
+        final String keyPattern = nju.seec.helper.util.StringUtils.keyPattern(key);
+
+        return this.findAll(
+                (Specification<Course>) (root, query, cb) -> cb.and(
+                        root.get("id").in(courseIds)
+                        , cb.or(cb.like(root.get("name"), keyPattern), cb.like(root.join("teacher", JoinType.LEFT).get("name"), keyPattern))
+                ), pageable);
+    }
 }

+ 2 - 2
src/main/java/nju/seec/helper/dao/CourseFileDAO.java

@@ -1,8 +1,8 @@
 package nju.seec.helper.dao;
 
 import nju.seec.helper.entity.CourseFile;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.exception.HelperException;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;

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

@@ -1,6 +1,7 @@
 package nju.seec.helper.dao;
 
 import nju.seec.helper.entity.Message;
+import nju.seec.helper.enums.MessageType;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
@@ -48,4 +49,12 @@ public interface MessageDAO extends JpaRepository<Message, Long> {
      * @return
      */
     Page<Message> findByToUserIdAndReadAndContentContains(Long toUserId, Boolean read, String key, Pageable pageable);
+
+    /**
+     * 基于关联数据删除
+     *
+     * @param refId
+     * @param messageType
+     */
+    void deleteByRefIdAndType(Long refId, MessageType messageType);
 }

+ 25 - 2
src/main/java/nju/seec/helper/dao/NoticeDAO.java

@@ -1,8 +1,12 @@
 package nju.seec.helper.dao;
 
 import nju.seec.helper.entity.Notice;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.exception.HelperException;
+import nju.seec.helper.util.StringUtils;
+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;
@@ -22,4 +26,23 @@ public interface NoticeDAO extends JpaRepository<Notice, Long>, JpaSpecification
         return this.findById(id)
                 .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到公告"));
     }
+
+    /**
+     * 感觉课程ID、关键字检索
+     *
+     * @param courseId
+     * @param key
+     * @param pageable
+     * @return
+     */
+    default Page<Notice> findByCourseIdAndKey(Long courseId, String key, Pageable pageable) {
+        String keyPattern = StringUtils.keyPattern(key);
+
+        return this.findAll(
+                (Specification<Notice>) (root, query, cb) -> cb.and(
+                        cb.equal(root.get("courseId"), courseId)
+                        , cb.or(cb.like(root.get("title"), keyPattern), cb.like(root.get("content"), keyPattern))
+                ),
+                pageable);
+    }
 }

+ 49 - 16
src/main/java/nju/seec/helper/dao/QuizDAO.java

@@ -2,13 +2,19 @@ package nju.seec.helper.dao;
 
 import nju.seec.helper.entity.Quiz;
 import nju.seec.helper.entity.Slide;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.enums.QuizState;
+import nju.seec.helper.exception.HelperException;
+import nju.seec.helper.util.StringUtils;
+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 java.util.List;
+import java.util.Set;
 
 
 /**
@@ -26,27 +32,54 @@ public interface QuizDAO extends JpaRepository<Quiz, Long>, JpaSpecificationExec
      */
     default Quiz findQuizById(Long id) {
         return this.findById(id)
-                .filter(quiz -> quiz.getDeleteAt() == 0L)
                 .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到测试"));
     }
 
-//    Page<Quiz> findAllBySlideAndState(Slide slide, QuizState state, Pageable pageable);
-//
-//    Page<Quiz> findAllBySlide(Slide slide, Pageable pageable);
-
     /**
      * 根据课件检索
      *
      * @param slide
      * @return
      */
-    List<Quiz> findAllBySlide(Slide slide);
-
-//    Page<Quiz> findAllByCourseIn(Collection<Course> course, Pageable pageable);
-//
-//    Page<Quiz> findAllByCourseInAndState(Collection<Course> course, QuizState state, Pageable pageable);
-//
-//    Page<Quiz> findByTeacher(User teacher, Pageable pageable);
-//
-//    Page<Quiz> findByTeacherAndState(User teacher, QuizState state, Pageable pageable);
+    List<Quiz> findBySlide(Slide slide);
+
+    /**
+     * 根据课件、测试状态、名称检索
+     *
+     * @param slide
+     * @param quizStates
+     * @param name
+     * @param pageable
+     * @return
+     */
+    Page<Quiz> findBySlideAndStateNotInAndNameContains(Slide slide, Set<QuizState> quizStates, String name, Pageable pageable);
+
+    /**
+     * 根据课程、测试状态、名称检索
+     *
+     * @param courseIds
+     * @param quizState
+     * @param name
+     * @param pageable
+     * @return
+     */
+    default Page<Quiz> findByCourseIdsAndStateAndNameContains(Set<Long> courseIds, QuizState quizState, String name, Pageable pageable) {
+        return this.findAll(
+                (Specification<Quiz>) (root, query, cb) -> cb.and(
+                        root.get("course").get("id").in(courseIds)
+                        , cb.equal(root.get("state"), quizState)
+                        , cb.like(root.get("name"), StringUtils.keyPattern(name))
+                ),
+                pageable);
+    }
+
+    /**
+     * 根据课件、名称检索
+     *
+     * @param slide
+     * @param name
+     * @param pageable
+     * @return
+     */
+    Page<Quiz> findBySlideAndNameContains(Slide slide, String name, Pageable pageable);
 }

+ 9 - 8
src/main/java/nju/seec/helper/dao/QuizStudentAnswerDAO.java

@@ -8,6 +8,7 @@ import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
 
+import java.util.List;
 import java.util.Optional;
 
 /**
@@ -35,7 +36,7 @@ public interface QuizStudentAnswerDAO extends JpaRepository<QuizStudentAnswer, L
     Optional<QuizStudentAnswer> findByQuizAndStudent(Quiz quiz, User student);
 
     /**
-     * 根据测试检索
+     * 根据测试分页检索
      *
      * @param quiz
      * @param pageable
@@ -43,11 +44,11 @@ public interface QuizStudentAnswerDAO extends JpaRepository<QuizStudentAnswer, L
      */
     Page<QuizStudentAnswer> findByQuiz(Quiz quiz, Pageable pageable);
 
-//    Set<QuizStudentAnswer> findAllByQuiz(Quiz quiz);
-//
-//    Set<QuizStudentAnswer> findAllByStudent(User student);
-//
-//    Set<QuizStudentAnswer> findByStudentAndQuiz(User student, Quiz quiz);
-//
-//    Set<QuizStudentAnswer> findByStudentAndQuestionId(User student, String questionId);
+    /**
+     * 根据测试检索
+     *
+     * @param quiz
+     * @return
+     */
+    List<QuizStudentAnswer> findByQuiz(Quiz quiz);
 }

+ 2 - 2
src/main/java/nju/seec/helper/dao/ReplyDAO.java

@@ -1,8 +1,8 @@
 package nju.seec.helper.dao;
 
 import nju.seec.helper.entity.Reply;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.exception.HelperException;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
 

+ 41 - 5
src/main/java/nju/seec/helper/dao/SlideDAO.java

@@ -1,17 +1,23 @@
 package nju.seec.helper.dao;
 
+import nju.seec.helper.entity.Course;
 import nju.seec.helper.entity.Slide;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.entity.User;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.enums.SlideState;
+import nju.seec.helper.exception.HelperException;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.stereotype.Repository;
 
+import java.util.Set;
+
 /**
  * @author cst
  */
 @Repository
-public interface SlideDAO extends JpaRepository<Slide, Long>, JpaSpecificationExecutor<Slide> {
+public interface SlideDAO extends JpaRepository<Slide, Long> {
     /**
      * 封装findById
      *
@@ -20,7 +26,37 @@ public interface SlideDAO extends JpaRepository<Slide, Long>, JpaSpecificationEx
      */
     default Slide findSlideById(Long id) {
         return this.findById(id)
-                .filter(slide -> slide.getDeleteAt() == 0)
                 .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到课件"));
     }
+
+    /**
+     * 基于课程、课件状态、课件名检索
+     *
+     * @param course
+     * @param slideStates
+     * @param name
+     * @param pageable
+     * @return
+     */
+    Page<Slide> findByCourseAndStateNotInAndNameContains(Course course, Set<SlideState> slideStates, String name, Pageable pageable);
+
+    /**
+     * 基于课程、课件名检索
+     *
+     * @param course
+     * @param key
+     * @param pageable
+     * @return
+     */
+    Page<Slide> findByCourseAndNameContains(Course course, String key, Pageable pageable);
+
+    /**
+     * 基于教师、课件名检索
+     *
+     * @param teacher
+     * @param name
+     * @param pageable
+     * @return
+     */
+    Page<Slide> findByTeacherAndNameContains(User teacher, String name, Pageable pageable);
 }

+ 4 - 3
src/main/java/nju/seec/helper/dao/UserDAO.java

@@ -1,11 +1,12 @@
 package nju.seec.helper.dao;
 
 import nju.seec.helper.entity.User;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.exception.HelperException;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.stereotype.Repository;
 
 import java.util.Optional;
@@ -15,7 +16,7 @@ import java.util.Set;
  * @author cst
  */
 @Repository
-public interface UserDAO extends JpaRepository<User, Long> {
+public interface UserDAO extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
     /**
      * 封装findById
      *

+ 2 - 4
src/main/java/nju/seec/helper/dto/user/LoginDTO.java → src/main/java/nju/seec/helper/dto/common/LoginDTO.java

@@ -1,7 +1,6 @@
-package nju.seec.helper.dto.user;
+package nju.seec.helper.dto.common;
 
 import lombok.Data;
-import nju.seec.helper.dto.validator.schoolemail.SchoolEmail;
 
 import javax.validation.constraints.NotBlank;
 import java.io.Serializable;
@@ -12,8 +11,7 @@ import java.io.Serializable;
 @Data
 public class LoginDTO implements Serializable {
     private static final long serialVersionUID = -3359173125836901241L;
-    @NotBlank(message = "邮箱不能为空")
-    @SchoolEmail
+    @NotBlank(message = "登录名不能为空")
     private String username;
     @NotBlank(message = "密码不能为空")
     private String password;

+ 1 - 1
src/main/java/nju/seec/helper/dto/quiz/QuizDTO.java

@@ -2,7 +2,7 @@ package nju.seec.helper.dto.quiz;
 
 import lombok.Data;
 import nju.seec.helper.dto.groups.Create;
-import nju.seec.helper.util.enums.QuizType;
+import nju.seec.helper.enums.QuizType;
 import org.hibernate.validator.constraints.Length;
 
 import javax.validation.constraints.NotBlank;

+ 1 - 1
src/main/java/nju/seec/helper/dto/slide/SlideDTO.java

@@ -2,7 +2,7 @@ package nju.seec.helper.dto.slide;
 
 import lombok.Data;
 import nju.seec.helper.dto.groups.Create;
-import nju.seec.helper.util.enums.SlideState;
+import nju.seec.helper.enums.SlideState;
 import org.hibernate.validator.constraints.Length;
 import org.springframework.web.multipart.MultipartFile;
 

+ 1 - 1
src/main/java/nju/seec/helper/dto/slide/SlideStateDTO.java

@@ -1,7 +1,7 @@
 package nju.seec.helper.dto.slide;
 
 import lombok.Data;
-import nju.seec.helper.util.enums.SlideState;
+import nju.seec.helper.enums.SlideState;
 
 import javax.validation.constraints.NotNull;
 import java.io.Serializable;

+ 5 - 0
src/main/java/nju/seec/helper/dto/user/UserDTO.java

@@ -4,9 +4,11 @@ import lombok.Data;
 import nju.seec.helper.dto.groups.Create;
 import nju.seec.helper.dto.validator.phone.Phone;
 import nju.seec.helper.dto.validator.schoolemail.SchoolEmail;
+import nju.seec.helper.enums.UserType;
 import org.hibernate.validator.constraints.Length;
 
 import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 import java.io.Serializable;
 
 /**
@@ -31,4 +33,7 @@ public class UserDTO implements Serializable {
     @NotBlank(message = "密码不能为空", groups = Create.class)
     @Length(max = 50, message = "密码长度不能超过50位", groups = Create.class)
     private String password;
+
+    @NotNull(message = "缺少用户类型")
+    private UserType type;
 }

+ 2 - 2
src/main/java/nju/seec/helper/dto/validator/schoolemail/SchoolEmail.java

@@ -20,13 +20,13 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
 @Retention(RUNTIME)
 @Constraint(validatedBy = SchoolEmailValidator.class)
 public @interface SchoolEmail {
-    String message() default "该邮箱不是南大校邮";
+    String message() default "该邮箱不是校邮";
 
     Class<?>[] groups() default {};
 
     Class<? extends Payload>[] payload() default {};
 
-    String regexp() default ".+@(nju.edu.cn|smail.nju.edu.cn)";
+    String regexp() default ".+@.*(edu.cn)";
 
     Pattern.Flag[] flags() default {};
 }

+ 23 - 0
src/main/java/nju/seec/helper/entity/Admin.java

@@ -0,0 +1,23 @@
+package nju.seec.helper.entity;
+
+import lombok.Data;
+
+import javax.persistence.*;
+
+/**
+ * @author cst
+ */
+@Data
+@Entity
+@Table(name = "admin", uniqueConstraints = @UniqueConstraint(name = "username_unique", columnNames = "username"))
+public class Admin {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @Column(nullable = false)
+    private String username;
+
+    @Column(nullable = false)
+    private String password;
+}

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

@@ -11,8 +11,7 @@ import javax.persistence.*;
 @Data
 @Accessors(chain = true)
 @Entity
-@Table(name = "choose", indexes = @Index(name = "choose_student", columnList = "student_id"),
-        uniqueConstraints = @UniqueConstraint(name = "course_student_unique", columnNames = {"course_id", "student_id"}))
+@Table(name = "choose", uniqueConstraints = @UniqueConstraint(name = "student_course_unique", columnNames = {"student_id", "course_id"}))
 public class Choose {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)

+ 6 - 6
src/main/java/nju/seec/helper/entity/Comment.java

@@ -2,6 +2,7 @@ package nju.seec.helper.entity;
 
 import lombok.Data;
 import lombok.experimental.Accessors;
+import org.hibernate.annotations.ColumnDefault;
 import org.hibernate.annotations.CreationTimestamp;
 import org.hibernate.annotations.DynamicInsert;
 import org.hibernate.annotations.DynamicUpdate;
@@ -15,10 +16,7 @@ import java.time.LocalDateTime;
 @Data
 @Accessors(chain = true)
 @Entity
-@Table(name = "comment",
-        indexes = {@Index(name = "slide_page_top", columnList = "slide_id"),
-                @Index(name = "slide_page_top", columnList = "page_number"),
-                @Index(name = "slide_page_top", columnList = "top_number")})
+@Table(name = "comment")
 @DynamicUpdate
 @DynamicInsert
 public class Comment {
@@ -32,10 +30,12 @@ public class Comment {
     @Column(name = "page_number", nullable = false)
     private Integer pageNumber;
 
-    @Column(name = "`show`", nullable = false, columnDefinition = "bit(1) default true not null")
+    @ColumnDefault("true")
+    @Column(name = "`show`", nullable = false)
     private Boolean show = true;
 
-    @Column(name = "top_number", nullable = false, columnDefinition = "int default 0 not null")
+    @ColumnDefault("0")
+    @Column(name = "top_number", nullable = false)
     private Integer topNumber = 0;
 
     @Column(nullable = false)

+ 9 - 6
src/main/java/nju/seec/helper/entity/Course.java

@@ -2,10 +2,12 @@ package nju.seec.helper.entity;
 
 import lombok.Data;
 import lombok.experimental.Accessors;
-import org.hibernate.annotations.CreationTimestamp;
-import org.hibernate.annotations.DynamicInsert;
-import org.hibernate.annotations.DynamicUpdate;
+import org.hibernate.annotations.*;
 
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.ForeignKey;
+import javax.persistence.Table;
 import javax.persistence.*;
 import java.time.LocalDateTime;
 
@@ -15,10 +17,10 @@ import java.time.LocalDateTime;
 @Data
 @Accessors(chain = true)
 @Entity
-@Table(name = "course",
-        uniqueConstraints = @UniqueConstraint(name = "course_name_unique", columnNames = {"teacher_id", "delete_at", "name"}))
+@Table(name = "course", uniqueConstraints = @UniqueConstraint(name = "course_name_unique", columnNames = {"delete_at", "teacher_id", "name"}))
 @DynamicUpdate
 @DynamicInsert
+@Where(clause = "delete_at = 0")
 public class Course {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -41,6 +43,7 @@ public class Course {
     @JoinColumn(name = "teacher_id", nullable = false, foreignKey = @ForeignKey(name = "course_teacher"))
     private User teacher;
 
-    @Column(name = "delete_at", columnDefinition = "bigint default 0 not null")
+    @ColumnDefault("0")
+    @Column(name = "delete_at", nullable = false)
     private Long deleteAt = 0L;
 }

+ 7 - 5
src/main/java/nju/seec/helper/entity/Message.java

@@ -2,7 +2,8 @@ package nju.seec.helper.entity;
 
 import lombok.Data;
 import lombok.experimental.Accessors;
-import nju.seec.helper.util.enums.MessageType;
+import nju.seec.helper.enums.MessageType;
+import org.hibernate.annotations.ColumnDefault;
 import org.hibernate.annotations.CreationTimestamp;
 import org.hibernate.annotations.DynamicInsert;
 import org.hibernate.annotations.DynamicUpdate;
@@ -16,7 +17,7 @@ import java.time.LocalDateTime;
 @Data
 @Accessors(chain = true)
 @Entity
-@Table(name = "message", indexes = {@Index(name = "message_user", columnList = "to_user_id")})
+@Table(name = "message")
 @DynamicUpdate
 @DynamicInsert
 public class Message {
@@ -31,13 +32,14 @@ public class Message {
     @Column(name = "to_user_id", nullable = false)
     private Long toUserId;
 
-    @Column(name = "slide_id")
-    private Long slideId;
+    @Column(name = "ref_id")
+    private Long refId;
 
     @Column(nullable = false)
     private String content;
 
-    @Column(name = "`read`", columnDefinition = "bit(1) default false not null")
+    @ColumnDefault("false")
+    @Column(name = "`read`", nullable = false)
     private Boolean read = false;
 
     @Column(name = "create_at", nullable = false, updatable = false)

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

@@ -14,7 +14,7 @@ import java.time.LocalDateTime;
 @Data
 @Accessors(chain = true)
 @Entity
-@Table(name = "notice", indexes = {@Index(name = "notice_course", columnList = "course_id")})
+@Table(name = "notice")
 public class Notice {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)

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

@@ -11,8 +11,7 @@ import javax.persistence.*;
 @Data
 @Accessors(chain = true)
 @Entity
-@Table(name = "question_record", indexes = @Index(name = "question_record_question", columnList = "question_id"),
-        uniqueConstraints = @UniqueConstraint(name = "teacher_question_unique", columnNames = {"teacher_id", "question_id"}))
+@Table(name = "question_record", uniqueConstraints = @UniqueConstraint(name = "teacher_question_unique", columnNames = {"teacher_id", "question_id"}))
 public class QuestionRecord {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)

+ 13 - 12
src/main/java/nju/seec/helper/entity/Quiz.java

@@ -2,14 +2,14 @@ package nju.seec.helper.entity;
 
 import lombok.Data;
 import lombok.experimental.Accessors;
-import nju.seec.helper.entity.converter.ListConverter;
-import nju.seec.helper.util.enums.QuizState;
-import nju.seec.helper.util.enums.QuizType;
-import org.hibernate.annotations.CreationTimestamp;
-import org.hibernate.annotations.DynamicInsert;
-import org.hibernate.annotations.DynamicUpdate;
-import org.hibernate.annotations.UpdateTimestamp;
+import nju.seec.helper.enums.QuizState;
+import nju.seec.helper.enums.QuizType;
+import org.hibernate.annotations.*;
 
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.ForeignKey;
+import javax.persistence.Table;
 import javax.persistence.*;
 import java.time.LocalDateTime;
 import java.util.List;
@@ -22,9 +22,10 @@ import java.util.List;
 @Data
 @Accessors(chain = true)
 @Entity
-@Table(name = "quiz", uniqueConstraints = @UniqueConstraint(name = "quiz_name_unique", columnNames = {"slide_id", "delete_at", "name",}))
+@Table(name = "quiz", uniqueConstraints = @UniqueConstraint(name = "quiz_name_unique", columnNames = {"delete_at", "slide_id", "name",}))
 @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)")
 public class Quiz {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -47,7 +48,8 @@ public class Quiz {
     @JoinColumn(name = "teacher_id", foreignKey = @ForeignKey(name = "quiz_teacher"))
     private User teacher;
 
-    @Convert(converter = ListConverter.class)
+    @Type(type = "json")
+    @Column(columnDefinition = "json", nullable = false)
     private List<String> questions;
 
     @Enumerated(value = EnumType.STRING)
@@ -61,16 +63,15 @@ public class Quiz {
     @Column(name = "max_submit_number", nullable = false)
     private Integer maxSubmitNumber;
 
-    @Basic
     @Column(name = "create_at", nullable = false, updatable = false)
     @CreationTimestamp
     private LocalDateTime createAt;
 
-    @Basic
     @Column(name = "update_at", nullable = false)
     @UpdateTimestamp
     private LocalDateTime updateAt;
 
-    @Column(name = "delete_at", nullable = false, columnDefinition = "bigint default 0 not null")
+    @ColumnDefault("0")
+    @Column(name = "delete_at", nullable = false)
     private Long deleteAt = 0L;
 }

+ 9 - 8
src/main/java/nju/seec/helper/entity/QuizStudentAnswer.java

@@ -3,11 +3,12 @@ package nju.seec.helper.entity;
 
 import lombok.Data;
 import lombok.experimental.Accessors;
-import nju.seec.helper.entity.converter.MapConverter;
-import org.hibernate.annotations.DynamicInsert;
-import org.hibernate.annotations.DynamicUpdate;
-import org.hibernate.annotations.UpdateTimestamp;
+import org.hibernate.annotations.*;
 
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.ForeignKey;
+import javax.persistence.Table;
 import javax.persistence.*;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
@@ -38,16 +39,16 @@ public class QuizStudentAnswer {
     @JoinColumn(name = "student_id", foreignKey = @ForeignKey(name = "answer_student"))
     private User student;
 
-    @SuppressWarnings("all")
-    @Convert(converter = MapConverter.class)
-    @Column(nullable = false)
+    @Type(type = "json")
+    @Column(columnDefinition = "json", nullable = false)
     private Map<String, Object> answers;
 
     @Column(name = "submit_at", nullable = false)
     @UpdateTimestamp
     private LocalDateTime submitAt;
 
-    @Column(name = "submit_number", nullable = false, columnDefinition = "int default 1 not null")
+    @ColumnDefault("1")
+    @Column(name = "submit_number", nullable = false)
     private Integer submitNumber = 0;
 
     @Column(nullable = false)

+ 10 - 8
src/main/java/nju/seec/helper/entity/Slide.java

@@ -2,12 +2,13 @@ package nju.seec.helper.entity;
 
 import lombok.Data;
 import lombok.experimental.Accessors;
-import nju.seec.helper.util.enums.SlideState;
-import org.hibernate.annotations.CreationTimestamp;
-import org.hibernate.annotations.DynamicInsert;
-import org.hibernate.annotations.DynamicUpdate;
-import org.hibernate.annotations.UpdateTimestamp;
+import nju.seec.helper.enums.SlideState;
+import org.hibernate.annotations.*;
 
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.ForeignKey;
+import javax.persistence.Table;
 import javax.persistence.*;
 import java.time.LocalDateTime;
 
@@ -17,10 +18,10 @@ import java.time.LocalDateTime;
 @Data
 @Accessors(chain = true)
 @Entity
-@Table(name = "slide",
-        uniqueConstraints = @UniqueConstraint(name = "slide_name_unique", columnNames = {"course_id", "delete_at", "name",}))
+@Table(name = "slide", uniqueConstraints = @UniqueConstraint(name = "slide_name_unique", columnNames = {"delete_at", "course_id", "name"}))
 @DynamicUpdate
 @DynamicInsert
+@Where(clause = "delete_at = 0 and course_id in (select c.id from course c where c.delete_at = 0)")
 public class Slide {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -55,6 +56,7 @@ public class Slide {
     @Column(nullable = false)
     private Integer pages;
 
-    @Column(name = "delete_at", columnDefinition = "bigint default 0 not null")
+    @ColumnDefault("0")
+    @Column(name = "delete_at", nullable = false)
     private Long deleteAt = 0L;
 }

+ 12 - 6
src/main/java/nju/seec/helper/entity/User.java

@@ -2,7 +2,9 @@ package nju.seec.helper.entity;
 
 import lombok.Data;
 import lombok.experimental.Accessors;
-import nju.seec.helper.util.enums.UserType;
+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;
@@ -16,9 +18,8 @@ import java.time.LocalDateTime;
 @Data
 @Accessors(chain = true)
 @Entity
-@Table(name = "user",
-        uniqueConstraints = {@UniqueConstraint(name = "user_phone_unique", columnNames = "phone"),
-                @UniqueConstraint(name = "user_email_unique", columnNames = "email")})
+@Table(name = "user", uniqueConstraints = {@UniqueConstraint(name = "user_phone_unique", columnNames = "phone"),
+        @UniqueConstraint(name = "user_email_unique", columnNames = "email")})
 @DynamicUpdate
 @DynamicInsert
 public class User {
@@ -29,14 +30,14 @@ public class User {
     @Column(nullable = false)
     private String name;
 
-    @Column(nullable = false, unique = true)
+    @Column(nullable = false)
     private String email;
 
     @Enumerated(EnumType.STRING)
     @Column(nullable = false, updatable = false)
     private UserType type;
 
-    @Column(nullable = false, unique = true)
+    @Column(nullable = false)
     private String phone;
 
     @Column(nullable = false)
@@ -45,4 +46,9 @@ public class User {
     @Column(name = "create_at", updatable = false, nullable = false)
     @CreationTimestamp
     private LocalDateTime createAt;
+
+    @ColumnDefault("'NORMAL'")
+    @Enumerated(EnumType.STRING)
+    @Column(nullable = false)
+    private UserState state;
 }

+ 0 - 23
src/main/java/nju/seec/helper/entity/converter/ListConverter.java

@@ -1,23 +0,0 @@
-package nju.seec.helper.entity.converter;
-
-import com.google.gson.reflect.TypeToken;
-import nju.seec.helper.util.JsonUtils;
-
-import javax.persistence.AttributeConverter;
-import java.util.List;
-
-/**
- * @author cst
- */
-public class ListConverter implements AttributeConverter<List<String>, String> {
-    @Override
-    public String convertToDatabaseColumn(List<String> attribute) {
-        return JsonUtils.toJson(attribute);
-    }
-
-    @Override
-    public List<String> convertToEntityAttribute(String dbData) {
-        return JsonUtils.fromJson(dbData, new TypeToken<List<String>>() {
-        });
-    }
-}

+ 0 - 23
src/main/java/nju/seec/helper/entity/converter/MapConverter.java

@@ -1,23 +0,0 @@
-package nju.seec.helper.entity.converter;
-
-import com.google.gson.reflect.TypeToken;
-import nju.seec.helper.util.JsonUtils;
-
-import javax.persistence.AttributeConverter;
-import java.util.Map;
-
-/**
- * @author cst
- */
-public class MapConverter implements AttributeConverter<Map<String, Object>, String> {
-    @Override
-    public String convertToDatabaseColumn(Map<String, Object> attribute) {
-        return JsonUtils.toJson(attribute);
-    }
-
-    @Override
-    public Map<String, Object> convertToEntityAttribute(String dbData) {
-        return JsonUtils.fromJson(dbData, new TypeToken<Map<String, Object>>() {
-        });
-    }
-}

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

@@ -0,0 +1,5 @@
+@TypeDef(name = "json", typeClass = JsonStringType.class)
+package nju.seec.helper.entity;
+
+import com.vladmihalcea.hibernate.type.json.JsonStringType;
+import org.hibernate.annotations.TypeDef;

+ 1 - 10
src/main/java/nju/seec/helper/util/enums/ExceptionType.java → src/main/java/nju/seec/helper/enums/ExceptionType.java

@@ -1,4 +1,4 @@
-package nju.seec.helper.util.enums;
+package nju.seec.helper.enums;
 
 import lombok.Getter;
 import org.springframework.http.HttpStatus;
@@ -17,13 +17,4 @@ public enum ExceptionType {
     ExceptionType(HttpStatus status) {
         this.status = status;
     }
-
-    public static ExceptionType valueOf(HttpStatus status) {
-        for (ExceptionType exceptionType : ExceptionType.values()) {
-            if (exceptionType.getStatus().equals(status)) {
-                return exceptionType;
-            }
-        }
-        return ExceptionType.ERROR;
-    }
 }

+ 1 - 1
src/main/java/nju/seec/helper/util/enums/MessageType.java → src/main/java/nju/seec/helper/enums/MessageType.java

@@ -1,4 +1,4 @@
-package nju.seec.helper.util.enums;
+package nju.seec.helper.enums;
 
 /**
  * @author cst

+ 9 - 7
src/main/java/nju/seec/helper/util/enums/QuestionType.java → src/main/java/nju/seec/helper/enums/QuestionType.java

@@ -1,7 +1,11 @@
-package nju.seec.helper.util.enums;
+package nju.seec.helper.enums;
 
 import com.fasterxml.jackson.annotation.JsonValue;
 
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
 /**
  * @author cst
  */
@@ -17,6 +21,9 @@ public enum QuestionType {
 
     private String value;
 
+    private final static Map<String, QuestionType> VALUE_TYPE_MAP =
+            Stream.of(QuestionType.values()).collect(Collectors.toMap(QuestionType::getValue, questionType -> questionType));
+
     QuestionType(String value) {
         this.value = value;
     }
@@ -27,11 +34,6 @@ public enum QuestionType {
     }
 
     public static QuestionType getQuestionType(String value) {
-        for (QuestionType questionType : QuestionType.values()) {
-            if (questionType.getValue().equals(value)) {
-                return questionType;
-            }
-        }
-        return null;
+        return VALUE_TYPE_MAP.get(value);
     }
 }

+ 23 - 0
src/main/java/nju/seec/helper/enums/QuizState.java

@@ -0,0 +1,23 @@
+package nju.seec.helper.enums;
+
+/**
+ * @author xst
+ * <p>
+ * updated by cst
+ */
+public enum QuizState {
+    NOT_STARTED("未开始"), ONGOING("正在进行"), CLOSED("已结束");
+    private String name;
+
+    QuizState(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}

+ 19 - 0
src/main/java/nju/seec/helper/enums/QuizType.java

@@ -0,0 +1,19 @@
+package nju.seec.helper.enums;
+
+/**
+ * @author cst
+ */
+public enum QuizType {
+    /**
+     * 课前测试
+     */
+    BEFORE_CLASS,
+    /**
+     * 课中测试
+     */
+    IN_CLASS,
+    /**
+     * 课后测试
+     */
+    AFTER_CLASS
+}

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

@@ -1,4 +1,4 @@
-package nju.seec.helper.util.enums;
+package nju.seec.helper.enums;
 
 /**
  * @author cst

+ 8 - 0
src/main/java/nju/seec/helper/enums/UserState.java

@@ -0,0 +1,8 @@
+package nju.seec.helper.enums;
+
+/**
+ * @author cst
+ */
+public enum UserState {
+    CHECKING, NORMAL, LOCKED
+}

+ 9 - 0
src/main/java/nju/seec/helper/enums/UserType.java

@@ -0,0 +1,9 @@
+package nju.seec.helper.enums;
+
+/**
+ * @author cst
+ */
+
+public enum UserType {
+    TEACHER, STUDENT, ADMIN
+}

+ 2 - 2
src/main/java/nju/seec/helper/util/exception/HelperException.java → src/main/java/nju/seec/helper/exception/HelperException.java

@@ -1,8 +1,8 @@
-package nju.seec.helper.util.exception;
+package nju.seec.helper.exception;
 
 import lombok.AllArgsConstructor;
 import lombok.Getter;
-import nju.seec.helper.util.enums.ExceptionType;
+import nju.seec.helper.enums.ExceptionType;
 
 /**
  * @author cst

+ 58 - 0
src/main/java/nju/seec/helper/service/AdminService.java

@@ -0,0 +1,58 @@
+package nju.seec.helper.service;
+
+import nju.seec.helper.aspect.auth.LoginUser;
+import nju.seec.helper.enums.UserState;
+import nju.seec.helper.enums.UserType;
+import nju.seec.helper.vo.AdminVO;
+import nju.seec.helper.vo.UserVO;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+/**
+ * @author cst
+ */
+public interface AdminService {
+    /**
+     * 用户名密码获取
+     *
+     * @param username
+     * @param password
+     * @return
+     */
+    AdminVO getAdminByUsernameAndPassword(String username, String password);
+
+    /**
+     * 获取用户列表
+     *
+     * @param type
+     * @param state
+     * @param key
+     * @param pageable
+     * @return
+     */
+    Page<UserVO> getUsers(UserType type, UserState state, String key, Pageable pageable);
+
+    /**
+     * 审核通过用户
+     *
+     * @param admin
+     * @param userId
+     */
+    void passUser(LoginUser admin, Long userId);
+
+    /**
+     * 封禁用户
+     *
+     * @param admin
+     * @param userId
+     */
+    void lockUser(LoginUser admin, Long userId);
+
+    /**
+     * 解封用户
+     *
+     * @param admin
+     * @param userId
+     */
+    void unlockUser(LoginUser admin, Long userId);
+}

+ 46 - 0
src/main/java/nju/seec/helper/service/AuthService.java

@@ -0,0 +1,46 @@
+package nju.seec.helper.service;
+
+import nju.seec.helper.aspect.auth.LoginUser;
+
+/**
+ * 数据权限检查
+ *
+ * @author cst
+ */
+public interface AuthService<T> {
+    /**
+     * 检查新增权限
+     *
+     * @param user
+     * @param t
+     * @return
+     */
+    boolean existsAddAuth(LoginUser user, T t);
+
+    /**
+     * 检查删除权限
+     *
+     * @param user
+     * @param t
+     * @return
+     */
+    boolean existsDelAuth(LoginUser user, T t);
+
+    /**
+     * 检查更新权限
+     *
+     * @param user
+     * @param t
+     * @return
+     */
+    boolean existsUpdateAuth(LoginUser user, T t);
+
+    /**
+     * 检查获取权限
+     *
+     * @param user
+     * @param t
+     * @return
+     */
+    boolean existsGetAuth(LoginUser user, T t);
+}

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

@@ -1,7 +1,8 @@
 package nju.seec.helper.service;
 
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.comment.CommentDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.entity.Comment;
 import nju.seec.helper.vo.CommentVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -9,7 +10,7 @@ import org.springframework.data.domain.Pageable;
 /**
  * @author cst
  */
-public interface CommentService {
+public interface CommentService extends AuthService<Comment> {
     /**
      * 创建评论
      *
@@ -56,8 +57,9 @@ public interface CommentService {
     /**
      * 获得某条评论
      *
+     * @param user
      * @param commentId
      * @return
      */
-    CommentVO getOneComment(Long commentId);
+    CommentVO getOneComment(LoginUser user, Long commentId);
 }

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

@@ -1,7 +1,8 @@
 package nju.seec.helper.service;
 
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.course.CourseFileDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.entity.CourseFile;
 import nju.seec.helper.vo.CourseFileVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -9,7 +10,7 @@ import org.springframework.data.domain.Pageable;
 /**
  * @author cst
  */
-public interface CourseFileService {
+public interface CourseFileService extends AuthService<CourseFile> {
     /**
      * 上传课程附件
      *
@@ -24,7 +25,7 @@ public interface CourseFileService {
      * @param user
      * @param courseFileId
      */
-    void  deleteCourseFile(LoginUser user, Long courseFileId);
+    void deleteCourseFile(LoginUser user, Long courseFileId);
 
     /**
      * 取得课程附件

+ 15 - 13
src/main/java/nju/seec/helper/service/CourseService.java

@@ -1,8 +1,9 @@
 package nju.seec.helper.service;
 
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.course.ChooseDTO;
 import nju.seec.helper.dto.course.CourseDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.entity.Course;
 import nju.seec.helper.vo.CourseVO;
 import nju.seec.helper.vo.UserVO;
 import org.springframework.data.domain.Page;
@@ -11,7 +12,7 @@ import org.springframework.data.domain.Pageable;
 /**
  * @author cst
  */
-public interface CourseService {
+public interface CourseService extends AuthService<Course> {
     /**
      * 创建课程
      *
@@ -66,14 +67,24 @@ public interface CourseService {
     Page<CourseVO> getCourses(String key, Pageable pageable);
 
     /**
-     * 获取课程列表
+     * 获取创建课程列表
      *
      * @param user
      * @param key
      * @param pageable
      * @return
      */
-    Page<CourseVO> getCourses(LoginUser user, String key, Pageable pageable);
+    Page<CourseVO> getCreatedCourses(LoginUser user, String key, Pageable pageable);
+
+    /**
+     * 获取选课列表
+     *
+     * @param user
+     * @param key
+     * @param pageable
+     * @return
+     */
+    Page<CourseVO> getChosenCourses(LoginUser user, String key, Pageable pageable);
 
     /**
      * 获取课程选课码
@@ -102,13 +113,4 @@ public interface CourseService {
      * @return
      */
     Page<UserVO> getChooseStudents(LoginUser user, Long courseId, String key, Pageable pageable);
-
-    /**
-     * 检查是否具有访问课程资源权限
-     *
-     * @param user
-     * @param courseId
-     * @return
-     */
-    boolean existsCourseResourcesGetAuth(LoginUser user, Long courseId);
 }

+ 13 - 5
src/main/java/nju/seec/helper/service/MessageService.java

@@ -1,9 +1,9 @@
 package nju.seec.helper.service;
 
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.message.MessageDTO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.entity.Message;
-import nju.seec.helper.util.enums.MessageType;
+import nju.seec.helper.enums.MessageType;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 
@@ -19,9 +19,9 @@ public interface MessageService {
      * @param toUserIds
      * @param type
      * @param content
-     * @param slideId
+     * @param refId
      */
-    void createMessage(Set<Long> toUserIds, MessageType type, String content, Long slideId);
+    void createMessage(Set<Long> toUserIds, MessageType type, String content, Long refId);
 
     /**
      * 删除消息
@@ -29,7 +29,15 @@ public interface MessageService {
      * @param user
      * @param messageDTO
      */
-    void  deleteMessages(LoginUser user, MessageDTO messageDTO);
+    void deleteMessages(LoginUser user, MessageDTO messageDTO);
+
+    /**
+     * 删除关联消息
+     *
+     * @param refId
+     * @param messageType
+     */
+    void deleteMessages(Long refId, MessageType messageType);
 
     /**
      * 设置消息已阅

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

@@ -1,7 +1,8 @@
 package nju.seec.helper.service;
 
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.notice.NoticeDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.entity.Notice;
 import nju.seec.helper.vo.NoticeVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -9,7 +10,7 @@ import org.springframework.data.domain.Pageable;
 /**
  * @author cst
  */
-public interface NoticeService {
+public interface NoticeService extends AuthService<Notice> {
     /**
      * 创建公告
      *
@@ -46,7 +47,7 @@ public interface NoticeService {
      * @param user
      * @param noticeId
      */
-    void  deleteNotice(LoginUser user, Long noticeId);
+    void deleteNotice(LoginUser user, Long noticeId);
 
     /**
      * 取得某一公告

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

@@ -1,7 +1,7 @@
 package nju.seec.helper.service;
 
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.question.BaseQuestionDTO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.entity.Quiz;
 import nju.seec.helper.vo.question.BaseQuestionVO;
 import org.springframework.data.domain.Page;

+ 5 - 82
src/main/java/nju/seec/helper/service/QuizService.java

@@ -1,12 +1,10 @@
 package nju.seec.helper.service;
 
-//import nju.seec.helper.dto.quiz.QuizDTO;
-//import nju.seec.helper.vo.quiz.QuizVO;
-
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.quiz.QuizDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.entity.Quiz;
 import nju.seec.helper.entity.Slide;
-import nju.seec.helper.util.enums.QuizState;
+import nju.seec.helper.enums.QuizState;
 import nju.seec.helper.vo.quiz.QuizVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -16,7 +14,7 @@ import org.springframework.data.domain.Pageable;
  * <p>
  * updated by cst
  */
-public interface QuizService {
+public interface QuizService extends AuthService<Quiz> {
 
     /**
      * 创建测试
@@ -44,7 +42,7 @@ public interface QuizService {
      * @param quizId
      * @return
      */
-    void  deleteQuiz(LoginUser user, Long quizId);
+    void deleteQuiz(LoginUser user, Long quizId);
 
     /**
      * 教师基于课件获取测试
@@ -94,79 +92,4 @@ public interface QuizService {
      * @param slide
      */
     void modifyQuizState(Slide slide);
-
-//    /**
-//     * @param quizId
-//     * @param uid
-//     * @param answers
-//     * @return
-//     */
-//    Integer upStudentAnswer(Long quizId, Long uid, List<QuestionStudentAnswerDTO> answers);
-
-//    /**
-//     * @param slideId
-//     * @param pageable
-//     * @param state
-//     * @param uid
-//     * @return
-//     */
-//    Page<Quiz> getAllBasicQuizBySlide(Long slideId, Pageable pageable, QuizState state, Long uid);
-
-//    /**
-//     * @param quiz
-//     * @param slide
-//     * @return
-//     */
-//    QuizState checkQuizState(Quiz quiz, Slide slide);
-
-//    /**
-//     * @param quizId
-//     * @param slideId
-//     * @return
-//     */
-//    QuizState checkQuizState(Long quizId, Long slideId);
-
-//    /**
-//     * @param quizId
-//     * @return
-//     */
-//    QuizVO combineQuiz(Long quizId);
-
-//    /**
-//     * @param quizId
-//     * @param uid
-//     * @param type
-//     * @return
-//     */
-//    QuizVO combineQuizWithAnswer(Long quizId, Long uid, UserType type);
-//
-//
-//    /**
-//     * @param quizId
-//     * @param name
-//     * @param questions
-//     * @param quizTime
-//     * @param slideId
-//     * @return
-//     */
-//    Quiz modifyQuiz(Long quizId, String name, List<String> questions, SlideState quizTime, String slideId);
-//
-//    /**
-//     * @param pageable
-//     * @param state
-//     * @param uid
-//     * @return
-//     */
-//    Page<Quiz> getAllBasicQuizByUser(Pageable pageable, QuizState state, Long uid);
-//
-//    /**
-//     * @param aLong
-//     * @return
-//     */
-//    List<QuizResultVO> getQuizResult(Long aLong);
-//
-//    /**
-//     * @param slide
-//     */
-//    void modifySlideState(Slide slide);
 }

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

@@ -1,7 +1,8 @@
 package nju.seec.helper.service;
 
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.quiz.QuizStudentAnswerDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.vo.QuizStudentAnswerStatisticVO;
 import nju.seec.helper.vo.quiz.QuizStudentAnswerVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -28,7 +29,7 @@ public interface QuizStudentAnswerService {
      * @param pageable
      * @return
      */
-    Page<QuizStudentAnswerVO> teacherGetQuizStudentAnswers(LoginUser user, Long quizId, Pageable pageable);
+    Page<QuizStudentAnswerVO> getQuizStudentAnswers(LoginUser user, Long quizId, Pageable pageable);
 
     /**
      * 学生查看自己的答案
@@ -37,5 +38,14 @@ public interface QuizStudentAnswerService {
      * @param quizId
      * @return
      */
-    QuizStudentAnswerVO studentGetSelfQuizStudentAnswer(LoginUser user, Long quizId);
+    QuizStudentAnswerVO getQuizStudentAnswer(LoginUser user, Long quizId);
+
+    /**
+     * 获取测试作答的统计信息
+     *
+     * @param user
+     * @param quizId
+     * @return
+     */
+    QuizStudentAnswerStatisticVO getQuizStudentAnswerStatistic(LoginUser user, Long quizId);
 }

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

@@ -1,13 +1,14 @@
 package nju.seec.helper.service;
 
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.reply.ReplyDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.entity.Reply;
 import nju.seec.helper.vo.ReplyVO;
 
 /**
  * @author cst
  */
-public interface ReplyService {
+public interface ReplyService extends AuthService<Reply> {
     /**
      * 创建回复
      *

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

@@ -1,9 +1,10 @@
 package nju.seec.helper.service;
 
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.slide.SlideDTO;
 import nju.seec.helper.dto.slide.SlideFileDTO;
 import nju.seec.helper.dto.slide.SlideStateDTO;
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.entity.Slide;
 import nju.seec.helper.vo.SlideVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -11,7 +12,7 @@ import org.springframework.data.domain.Pageable;
 /**
  * @author cst
  */
-public interface SlideService {
+public interface SlideService extends AuthService<Slide> {
     /**
      * 创建课件
      *

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

@@ -1,6 +1,6 @@
 package nju.seec.helper.service;
 
-import nju.seec.helper.dto.user.LoginUser;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dto.user.ResetPasswordDTO;
 import nju.seec.helper.dto.user.UserDTO;
 import nju.seec.helper.vo.UserVO;

+ 86 - 0
src/main/java/nju/seec/helper/service/impl/AdminServiceImpl.java

@@ -0,0 +1,86 @@
+package nju.seec.helper.service.impl;
+
+import nju.seec.helper.aspect.auth.LoginUser;
+import nju.seec.helper.dao.AdminDAO;
+import nju.seec.helper.dao.UserDAO;
+import nju.seec.helper.entity.User;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.enums.UserState;
+import nju.seec.helper.enums.UserType;
+import nju.seec.helper.exception.HelperException;
+import nju.seec.helper.service.AdminService;
+import nju.seec.helper.util.EncryptUtils;
+import nju.seec.helper.util.StringUtils;
+import nju.seec.helper.vo.AdminVO;
+import nju.seec.helper.vo.UserVO;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author cst
+ */
+@Service
+public class AdminServiceImpl implements AdminService {
+    private final AdminDAO adminDAO;
+    private final UserDAO userDAO;
+
+    public AdminServiceImpl(AdminDAO adminDAO, UserDAO userDAO) {
+        this.adminDAO = adminDAO;
+        this.userDAO = userDAO;
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public AdminVO getAdminByUsernameAndPassword(String username, String password) {
+        return adminDAO.findByUsernameAndPassword(username, EncryptUtils.encode(password))
+                .map(AdminVO::new)
+                .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "用户名密码错误"));
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public Page<UserVO> getUsers(UserType type, UserState state, String key, Pageable pageable) {
+        return findUsersByTypeAndStateAndKey(type, state, key, pageable).map(UserVO::new);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void passUser(LoginUser admin, Long userId) {
+        User user = userDAO.findUserById(userId);
+        if (user.getState() != UserState.CHECKING) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "该帐号暂时无法审核通过");
+        }
+        userDAO.save(user.setState(UserState.NORMAL));
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void lockUser(LoginUser admin, Long userId) {
+        userDAO.save(userDAO.findUserById(userId).setState(UserState.LOCKED));
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void unlockUser(LoginUser admin, Long userId) {
+        User user = userDAO.findUserById(userId);
+        if (user.getState() != UserState.LOCKED) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "该帐号暂时无法解封");
+        }
+        userDAO.save(user.setState(UserState.NORMAL));
+    }
+
+    private Page<User> findUsersByTypeAndStateAndKey(UserType userType, UserState userState, String key, Pageable pageable) {
+        final String keyPattern = StringUtils.keyPattern(key);
+
+        return userDAO.findAll(
+                (Specification<User>) (root, query, cb) -> cb.and(
+                        cb.equal(root.get("type").as(UserType.class), userType)
+                        , cb.equal(root.get("state").as(UserState.class), userState)
+                        , cb.or(cb.like(root.get("email"), keyPattern), cb.like(root.get("phone"), keyPattern), cb.like(root.get("name"), keyPattern))
+                )
+                , pageable);
+    }
+}

+ 59 - 41
src/main/java/nju/seec/helper/service/impl/CommentServiceImpl.java

@@ -1,20 +1,20 @@
 package nju.seec.helper.service.impl;
 
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.CommentDAO;
 import nju.seec.helper.dao.SlideDAO;
 import nju.seec.helper.dao.UserDAO;
 import nju.seec.helper.dto.comment.CommentDTO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.entity.Comment;
 import nju.seec.helper.entity.Slide;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.enums.MessageType;
+import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.service.CommentService;
 import nju.seec.helper.service.MessageService;
-import nju.seec.helper.service.util.AuthUtils;
-import nju.seec.helper.util.enums.MessageType;
 import nju.seec.helper.vo.CommentVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
-import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -42,16 +42,18 @@ public class CommentServiceImpl implements CommentService {
     @Transactional(rollbackFor = Exception.class)
     @Override
     public CommentVO createComment(LoginUser user, CommentDTO commentDTO) {
-        Comment comment = new Comment()
-                .setSlideId(commentDTO.getSlideId())
-                .setPageNumber(commentDTO.getPageNumber())
-                .setUser(userDAO.findUserById(user.getId()))
-                .setTitle(commentDTO.getTitle())
-                .setContent(commentDTO.getContent());
-        comment = commentDAO.save(comment);
+        Slide slide = slideDAO.findSlideById(commentDTO.getSlideId());
+
+        Comment comment = commentDAO.save(
+                new Comment()
+                        .setSlideId(slide.getId())
+                        .setPageNumber(commentDTO.getPageNumber())
+                        .setUser(userDAO.findUserById(user.getId()))
+                        .setTitle(commentDTO.getTitle())
+                        .setContent(commentDTO.getContent())
+        );
 
         // 增加通知
-        Slide slide = slideDAO.findSlideById(comment.getSlideId());
         if (!user.getId().equals(slide.getTeacher().getId())) {
             messageService.createMessage(Collections.singleton(slide.getTeacher().getId()), MessageType.COMMENT_NEW, String.format("[%s] - [%s] 在第%d页有新评论", slide.getCourse().getName(), slide.getName(), comment.getPageNumber()), slide.getId());
         }
@@ -64,7 +66,9 @@ public class CommentServiceImpl implements CommentService {
     public void deleteComment(LoginUser user, Long commentId) {
         Comment comment = commentDAO.findCommentById(commentId);
 
-        AuthUtils.checkDataAuth(user.getId(), comment.getUser().getId(), "您无权删除该评论");
+        if (!existsDelAuth(user, comment)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权删除该评论");
+        }
 
         unTopComment(comment);
         commentDAO.delete(comment);
@@ -75,19 +79,27 @@ public class CommentServiceImpl implements CommentService {
     public void topComment(LoginUser user, Long commentId) {
         Comment comment = commentDAO.findCommentById(commentId);
 
-        AuthUtils.checkDataAuth(user.getId(), slideDAO.findSlideById(comment.getSlideId()).getTeacher().getId(), "您无权在该评论区置顶评论");
+        if (!existsUpdateAuth(user, comment)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权在该评论区置顶评论");
+        }
 
         unTopComment(comment);
         comment.setTopNumber((int) countTopComments(comment.getSlideId(), comment.getPageNumber()) + 1);
         commentDAO.save(comment);
     }
 
+    private long countTopComments(Long slideId, Integer pageNumber) {
+        return commentDAO.countBySlideIdAndPageNumberAndTopNumberNot(slideId, pageNumber, 0);
+    }
+
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void unTopComment(LoginUser user, Long commentId) {
         Comment comment = commentDAO.findCommentById(commentId);
 
-        AuthUtils.checkDataAuth(user.getId(), slideDAO.findSlideById(comment.getSlideId()).getTeacher().getId(), "您无权在该评论区取消置顶评论");
+        if (!existsUpdateAuth(user, comment)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权在该评论区取消置顶评论");
+        }
 
         unTopComment(comment);
         commentDAO.save(comment);
@@ -96,46 +108,52 @@ public class CommentServiceImpl implements CommentService {
     @Transactional(readOnly = true)
     @Override
     public Page<CommentVO> getCommentsBySlideAndPageNumber(Long slideId, Integer pageNumber, Pageable pageable) {
-        return findBySlideIdAndPageNumber(slideId, pageNumber, pageable).map(CommentVO::new);
+        return commentDAO.findBySlideIdAndPageNumberAndShow(slideId, pageNumber, true, pageable).map(CommentVO::new);
     }
 
     @Transactional(readOnly = true)
     @Override
-    public CommentVO getOneComment(Long commentId) {
-        return new CommentVO(commentDAO.findCommentById(commentId));
+    public CommentVO getOneComment(LoginUser user, Long commentId) {
+        Comment comment = commentDAO.findCommentById(commentId);
+
+        if (!existsGetAuth(user, comment)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权参考该评论");
+        }
+
+        return new CommentVO(comment);
     }
 
     private void unTopComment(Comment comment) {
         Integer topNumber = comment.getTopNumber();
         comment.setTopNumber(0);
-        decTopNumber(comment.getSlideId(), comment.getPageNumber(), topNumber);
+        if (topNumber != 0) {
+            commentDAO.saveAll(
+                    commentDAO.findBySlideIdAndPageNumberAndTopNumberGreaterThan(comment.getSlideId(), comment.getPageNumber(), topNumber)
+                            .parallelStream()
+                            .peek(topComment -> topComment.setTopNumber(comment.getTopNumber() - 1))
+                            .collect(Collectors.toList())
+            );
+        }
     }
 
-    long countTopComments(Long slideId, Integer pageNumber) {
-        return commentDAO.count(
-                (Specification<Comment>) (root, query, cb) -> cb.and(
-                        cb.equal(root.get("slideId"), slideId),
-                        cb.equal(root.get("pageNumber"), pageNumber),
-                        cb.notEqual(root.get("topNumber"), 0)));
+    @Override
+    public boolean existsAddAuth(LoginUser user, Comment comment) {
+        return true;
     }
 
-    Page<Comment> findBySlideIdAndPageNumber(Long slideId, Integer pageNumber, Pageable pageable) {
-        return commentDAO.findAll(
-                (Specification<Comment>) (root, query, cb) -> cb.and(
-                        cb.equal(root.get("slideId"), slideId),
-                        cb.equal(root.get("pageNumber"), pageNumber)),
-                pageable);
+    @Override
+    public boolean existsDelAuth(LoginUser user, Comment comment) {
+        return comment.getUser().getId().equals(user.getId());
     }
 
-    void decTopNumber(Long slideId, Integer pageNumber, Integer topNumber) {
-        commentDAO.saveAll(
-                commentDAO
-                        .findAll((Specification<Comment>) (root, query, cb) -> cb.and(
-                                cb.equal(root.get("slideId"), slideId),
-                                cb.equal(root.get("pageNumber"), pageNumber),
-                                cb.gt(root.get("topNumber"), topNumber)))
-                        .parallelStream()
-                        .peek(comment -> comment.setTopNumber(comment.getTopNumber() - 1))
-                        .collect(Collectors.toSet()));
+    @Override
+    public boolean existsUpdateAuth(LoginUser user, Comment comment) {
+        return slideDAO.findSlideById(comment.getSlideId()).getTeacher().getId().equals(user.getId());
+    }
+
+    @Override
+    public boolean existsGetAuth(LoginUser user, Comment comment) {
+        return comment.getShow()
+                || comment.getUser().getId().equals(user.getId());
     }
 }

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

@@ -1,20 +1,18 @@
 package nju.seec.helper.service.impl;
 
 import lombok.SneakyThrows;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.CourseDAO;
 import nju.seec.helper.dao.CourseFileDAO;
 import nju.seec.helper.dto.course.CourseFileDTO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.entity.Course;
 import nju.seec.helper.entity.CourseFile;
+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.AuthUtils;
-import nju.seec.helper.util.Consts;
-import nju.seec.helper.util.FileUtils;
-import nju.seec.helper.util.RedisCacheUtils;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.service.util.OssObjectUrlUtils;
+import nju.seec.helper.util.OssUtils;
 import nju.seec.helper.vo.CourseFileVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -23,7 +21,6 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.File;
-import java.util.concurrent.TimeUnit;
 
 /**
  * @author cst
@@ -31,22 +28,21 @@ import java.util.concurrent.TimeUnit;
 @Service
 public class CourseFileServiceImpl implements CourseFileService {
     private static final String COURSE_FILE_STORE_DIR = "course_file";
-    private static final long COURSE_FILE_URL_LIVING_SECONDS = 20 * 60;
 
     private final CourseDAO courseDAO;
     private final CourseFileDAO courseFileDAO;
 
     private final CourseService courseService;
 
-    private final FileUtils fileUtils;
-    private final RedisCacheUtils cacheUtils;
+    private final OssUtils ossUtils;
+    private final OssObjectUrlUtils ossObjectUrlUtils;
 
-    public CourseFileServiceImpl(CourseDAO courseDAO, CourseFileDAO courseFileDAO, CourseService courseService, FileUtils fileUtils, RedisCacheUtils cacheUtils) {
+    public CourseFileServiceImpl(CourseDAO courseDAO, CourseFileDAO courseFileDAO, CourseService courseService, OssUtils ossUtils, OssObjectUrlUtils ossObjectUrlUtils) {
         this.courseDAO = courseDAO;
         this.courseFileDAO = courseFileDAO;
         this.courseService = courseService;
-        this.fileUtils = fileUtils;
-        this.cacheUtils = cacheUtils;
+        this.ossUtils = ossUtils;
+        this.ossObjectUrlUtils = ossObjectUrlUtils;
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -54,43 +50,50 @@ public class CourseFileServiceImpl implements CourseFileService {
     @Override
     public void uploadCourseFile(LoginUser user, CourseFileDTO courseFileDTO) {
         Course course = courseDAO.findCourseById(courseFileDTO.getCourseId());
-        AuthUtils.checkDataAuth(user.getId(), course.getTeacher().getId(), "您无权上传该课程的附件");
 
         MultipartFile file = courseFileDTO.getFile();
         String fileName = file.getOriginalFilename();
-
-        if (courseFileDAO.existsByCourseIdAndFileName(courseFileDTO.getCourseId(), fileName)) {
-            throw HelperException.of(ExceptionType.CONFLICT, "已有同名附件");
-        }
-
         String objectName = getCourseFileObjectName(course.getId(), fileName);
 
         CourseFile courseFile = new CourseFile()
                 .setCourseId(course.getId())
                 .setTeacherId(user.getId())
-                .setFileName(file.getOriginalFilename())
+                .setFileName(fileName)
                 .setObjectName(objectName);
 
+        if (!existsAddAuth(user, courseFile)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权上传该附件");
+        }
+
+        if (courseFileDAO.existsByCourseIdAndFileName(courseFileDTO.getCourseId(), fileName)) {
+            throw HelperException.of(ExceptionType.CONFLICT, "该课程已有同名附件");
+        }
+
         courseFileDAO.save(courseFile);
 
-        fileUtils.upload(objectName, file.getInputStream());
+        ossUtils.upload(objectName, file.getInputStream());
     }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void deleteCourseFile(LoginUser user, Long courseFileId) {
         CourseFile courseFile = courseFileDAO.findCourseFileById(courseFileId);
-        AuthUtils.checkDataAuth(user.getId(), courseFile.getTeacherId(), "您无权删除该附件");
+
+        if (!existsDelAuth(user, courseFile)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权删除该附件");
+        }
+
         courseFileDAO.delete(courseFile);
-        fileUtils.delete(courseFile.getObjectName());
+        ossUtils.delete(courseFile.getObjectName());
     }
 
     @Transactional(readOnly = true)
     @Override
     public Page<CourseFileVO> getCourseFilesByCourse(LoginUser user, Long courseId, String key, Pageable pageable) {
-        if (!courseService.existsCourseResourcesGetAuth(user, courseId)) {
+        if (!courseService.existsGetAuth(user, courseDAO.findCourseById(courseId))) {
             throw HelperException.of(ExceptionType.FORBIDDEN, "您无权访问该课程的附件");
         }
+
         return courseFileDAO.findByCourseIdAndFileNameContains(courseId, key, pageable).map(CourseFileVO::new);
     }
 
@@ -99,20 +102,36 @@ public class CourseFileServiceImpl implements CourseFileService {
     public String getCourseFileUrl(LoginUser user, Long courseFileId) {
         CourseFile courseFile = courseFileDAO.findCourseFileById(courseFileId);
 
-        if (!courseService.existsCourseResourcesGetAuth(user, courseFile.getCourseId())) {
+        System.currentTimeMillis();
+
+        if (!existsGetAuth(user, courseFile)) {
             throw HelperException.of(ExceptionType.FORBIDDEN, "您无权获取该附件的链接");
         }
 
-        String url = cacheUtils.get(Consts.COURSE_FILE_URL_CACHE_NAME, courseFile.getObjectName());
-        if (url != null) {
-            return url;
-        }
-        url = fileUtils.getUrl(courseFile.getObjectName(), COURSE_FILE_URL_LIVING_SECONDS);
-        cacheUtils.set(Consts.COURSE_FILE_URL_CACHE_NAME, courseFile.getObjectName(), url, COURSE_FILE_URL_LIVING_SECONDS, TimeUnit.SECONDS);
-        return url;
+        return ossObjectUrlUtils.getUrl(courseFile.getObjectName());
     }
 
     private String getCourseFileObjectName(Long courseId, String fileName) {
         return COURSE_FILE_STORE_DIR + File.separator + courseId + File.separator + fileName;
     }
+
+    @Override
+    public boolean existsAddAuth(LoginUser user, CourseFile courseFile) {
+        return courseDAO.findCourseById(courseFile.getCourseId()).getTeacher().getId().equals(user.getId());
+    }
+
+    @Override
+    public boolean existsDelAuth(LoginUser user, CourseFile courseFile) {
+        return courseFile.getTeacherId().equals(user.getId());
+    }
+
+    @Override
+    public boolean existsUpdateAuth(LoginUser user, CourseFile courseFile) {
+        return courseFile.getTeacherId().equals(user.getId());
+    }
+
+    @Override
+    public boolean existsGetAuth(LoginUser user, CourseFile courseFile) {
+        return courseService.existsGetAuth(user, courseDAO.findCourseById(courseFile.getCourseId()));
+    }
 }

+ 69 - 98
src/main/java/nju/seec/helper/service/impl/CourseServiceImpl.java

@@ -1,30 +1,26 @@
 package nju.seec.helper.service.impl;
 
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.ChooseDAO;
 import nju.seec.helper.dao.CourseDAO;
 import nju.seec.helper.dao.UserDAO;
 import nju.seec.helper.dto.course.ChooseDTO;
 import nju.seec.helper.dto.course.CourseDTO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.entity.Choose;
 import nju.seec.helper.entity.Course;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.enums.UserType;
+import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.service.CourseService;
-import nju.seec.helper.service.util.AuthUtils;
-import nju.seec.helper.service.util.StringUtils;
 import nju.seec.helper.util.EncryptUtils;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
 import nju.seec.helper.vo.CourseVO;
 import nju.seec.helper.vo.UserVO;
+import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
-import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import javax.persistence.criteria.JoinType;
-import java.util.Set;
-
 /**
  * @author cst
  */
@@ -43,16 +39,19 @@ public class CourseServiceImpl implements CourseService {
     @Transactional(rollbackFor = Exception.class)
     @Override
     public CourseVO createCourse(LoginUser teacher, CourseDTO courseDTO) {
-        // 检查
-        checkSameCourseName(0L, teacher.getId(), courseDTO.getName());
-
-        Course course = new Course()
-                .setTeacher(userDAO.findUserById(teacher.getId()))
-                .setName(courseDTO.getName())
-                .setBio(courseDTO.getBio())
-                .setCode(EncryptUtils.encode(courseDTO.getCode()));
-        course = courseDAO.save(course);
-        return new CourseVO(course);
+        try {
+            return new CourseVO(
+                    courseDAO.save(
+                            new Course()
+                                    .setTeacher(userDAO.findUserById(teacher.getId()))
+                                    .setName(courseDTO.getName())
+                                    .setBio(courseDTO.getBio())
+                                    .setCode(EncryptUtils.encode(courseDTO.getCode()))
+                    )
+            );
+        } catch (DataIntegrityViolationException e) {
+            throw HelperException.of(ExceptionType.CONFLICT, "创建失败,请检查课程信息是否正确且不与已有课程冲突");
+        }
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -60,22 +59,31 @@ public class CourseServiceImpl implements CourseService {
     public CourseVO modifyCourse(LoginUser user, Long courseId, CourseDTO courseDTO) {
         Course course = courseDAO.findCourseById(courseId);
 
-        // 检查
-        AuthUtils.checkDataAuth(user.getId(), course.getTeacher().getId(), "您无权修改该课程");
-        checkSameCourseName(course.getId(), user.getId(), courseDTO.getName());
+        if (!existsUpdateAuth(user, course)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权修改该课程");
+        }
 
-        course.setName(courseDTO.getName())
-                .setBio(courseDTO.getBio())
-                .setCode(EncryptUtils.encode(courseDTO.getCode()));
-        course = courseDAO.save(course);
-        return new CourseVO(course);
+        try {
+            return new CourseVO(
+                    courseDAO.save(
+                            course.setName(courseDTO.getName())
+                                    .setBio(courseDTO.getBio())
+                                    .setCode(EncryptUtils.encode(courseDTO.getCode()))
+                    )
+            );
+        } catch (DataIntegrityViolationException e) {
+            throw HelperException.of(ExceptionType.CONFLICT, "修改失败,请检查课程信息是否正确且不与已有课程冲突");
+        }
     }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void deleteCourse(LoginUser user, Long courseId) {
         Course course = courseDAO.findCourseById(courseId);
-        AuthUtils.checkDataAuth(user.getId(), course.getTeacher().getId(), "您无权删除该课程");
+
+        if (!existsUpdateAuth(user, course)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权删除该课程");
+        }
 
         course.setDeleteAt(System.currentTimeMillis());
         courseDAO.save(course);
@@ -85,6 +93,7 @@ public class CourseServiceImpl implements CourseService {
     @Override
     public void chooseCourse(LoginUser student, Long courseId, ChooseDTO chooseDTO) {
         Long studentId = student.getId();
+
         if (chooseDAO.existsByStudentIdAndCourseId(studentId, courseId)) {
             throw HelperException.of(ExceptionType.CONFLICT, "您已选课");
         }
@@ -93,10 +102,11 @@ public class CourseServiceImpl implements CourseService {
             throw HelperException.of(ExceptionType.NOT_FOUND, "选课码错误");
         }
 
-        Choose choose = new Choose()
-                .setCourseId(courseId)
-                .setStudentId(studentId);
-        chooseDAO.save(choose);
+        chooseDAO.save(
+                new Choose()
+                        .setCourseId(courseId)
+                        .setStudentId(studentId)
+        );
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -108,35 +118,29 @@ public class CourseServiceImpl implements CourseService {
     @Transactional(readOnly = true)
     @Override
     public Page<CourseVO> getCourses(String key, Pageable pageable) {
-        return findByKey(key, pageable).map(CourseVO::new);
+        return courseDAO.findByKey(key, pageable).map(CourseVO::new);
     }
 
     @Transactional(readOnly = true)
     @Override
-    public Page<CourseVO> getCourses(LoginUser user, String key, Pageable pageable) {
-        switch (user.getType()) {
-            case TEACHER:
-                return teacherGetCourses(user.getId(), key, pageable);
-            case STUDENT:
-                return studentGetCourses(user.getId(), key, pageable);
-            default:
-                return Page.empty();
-        }
-    }
-
-    private Page<CourseVO> teacherGetCourses(Long teacherId, String key, Pageable pageable) {
-        return findByTeacherIdAndKey(teacherId, key, pageable).map(CourseVO::new);
+    public Page<CourseVO> getCreatedCourses(LoginUser user, String key, Pageable pageable) {
+        return courseDAO.findByTeacherAndNameContains(userDAO.findUserById(user.getId()), key, pageable).map(CourseVO::new);
     }
 
-    private Page<CourseVO> studentGetCourses(Long studentId, String key, Pageable pageable) {
-        return findByIdsAndKey(chooseDAO.findCourseIdsByStudentId(studentId), key, pageable).map(CourseVO::new);
+    @Transactional(readOnly = true)
+    @Override
+    public Page<CourseVO> getChosenCourses(LoginUser user, String key, Pageable pageable) {
+        return courseDAO.findByIdsAndKey(chooseDAO.findCourseIdsByStudentId(user.getId()), key, pageable).map(CourseVO::new);
     }
 
     @Transactional(readOnly = true)
     @Override
     public String getCourseCode(LoginUser user, Long courseId) {
         Course course = courseDAO.findCourseById(courseId);
-        AuthUtils.checkDataAuth(user.getId(), course.getTeacher().getId(), "您无权获取该课程选课码");
+
+        if (!existsGetAuth(user, course)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权获取该课程选课码");
+        }
 
         return EncryptUtils.decode(course.getCode());
     }
@@ -151,65 +155,32 @@ public class CourseServiceImpl implements CourseService {
     @Override
     public Page<UserVO> getChooseStudents(LoginUser user, Long courseId, String key, Pageable pageable) {
         Course course = courseDAO.findCourseById(courseId);
-        AuthUtils.checkDataAuth(user.getId(), course.getTeacher().getId(), "您无权获取该课程的选课名单");
+
+        if (!existsGetAuth(user, course)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权获取该课程的选课名单");
+        }
 
         return userDAO.findByIdIn(chooseDAO.findStudentIdsByCourseId(courseId), pageable).map(UserVO::new);
     }
 
     @Override
-    public boolean existsCourseResourcesGetAuth(LoginUser user, Long courseId) {
-        switch (user.getType()) {
-            case TEACHER:
-                return courseDAO.findCourseById(courseId).getTeacher().getId().equals(user.getId());
-            case STUDENT:
-                return chooseDAO.existsByStudentIdAndCourseId(user.getId(), courseId);
-            default:
-                return false;
-        }
+    public boolean existsAddAuth(LoginUser user, Course course) {
+        return user.getType() == UserType.TEACHER;
     }
 
-    private void checkSameCourseName(Long courseId, Long teacherId, String name) {
-        long exists = courseDAO.count((Specification<Course>) (root, query, cb) ->
-                cb.and(cb.notEqual(root.get("id"), courseId)
-                        , cb.equal(root.get("teacher").get("id"), teacherId)
-                        , cb.equal(root.get("deleteAt"), 0L)
-                        , cb.equal(root.get("name"), name)
-                ));
-        if (exists > 0) {
-            throw HelperException.of(ExceptionType.CONFLICT, "该课程名已使用");
-        }
-    }
-
-    private Page<Course> findByTeacherIdAndKey(Long teacherId, String key, Pageable pageable) {
-        return courseDAO.findAll(
-                (Specification<Course>) (root, query, cb) -> cb.and(
-                        cb.equal(root.get("teacher").get("id"), teacherId)
-                        , cb.equal(root.get("deleteAt"), 0L)
-                        , cb.like(root.get("name"), StringUtils.keyPattern(key))
-                ),
-                pageable);
+    @Override
+    public boolean existsDelAuth(LoginUser user, Course course) {
+        return course.getTeacher().getId().equals(user.getId());
     }
 
-    private Page<Course> findByKey(String key, Pageable pageable) {
-        final String keyPattern = StringUtils.keyPattern(key);
-
-        return courseDAO.findAll(
-                (Specification<Course>) (root, query, cb) -> cb.and(
-                        cb.equal(root.get("deleteAt"), 0L)
-                        , cb.or(cb.like(root.get("name"), keyPattern), cb.like(root.join("teacher", JoinType.LEFT).get("name"), keyPattern))
-                ),
-                pageable);
+    @Override
+    public boolean existsUpdateAuth(LoginUser user, Course course) {
+        return course.getTeacher().getId().equals(user.getId());
     }
 
-    private Page<Course> findByIdsAndKey(Set<Long> courseIds, String key, Pageable pageable) {
-        final String keyPattern = StringUtils.keyPattern(key);
-
-        return courseDAO.findAll(
-                (Specification<Course>) (root, query, cb) -> cb.and(
-                        root.get("id").in(courseIds)
-                        , cb.equal(root.get("deleteAt"), 0L)
-                        , cb.or(cb.like(root.get("name"), keyPattern), cb.like(root.join("teacher", JoinType.LEFT).get("name"), keyPattern))
-                ),
-                pageable);
+    @Override
+    public boolean existsGetAuth(LoginUser user, Course course) {
+        return course.getTeacher().getId().equals(user.getId())
+                || chooseDAO.existsByStudentIdAndCourseId(user.getId(), course.getId());
     }
 }

+ 10 - 4
src/main/java/nju/seec/helper/service/impl/MessageServiceImpl.java

@@ -1,11 +1,11 @@
 package nju.seec.helper.service.impl;
 
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.MessageDAO;
 import nju.seec.helper.dto.message.MessageDTO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.entity.Message;
+import nju.seec.helper.enums.MessageType;
 import nju.seec.helper.service.MessageService;
-import nju.seec.helper.util.enums.MessageType;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.scheduling.annotation.Async;
@@ -28,14 +28,14 @@ public class MessageServiceImpl implements MessageService {
 
     @Async
     @Override
-    public void createMessage(Set<Long> toUserIds, MessageType type, String content, Long slideId) {
+    public void createMessage(Set<Long> toUserIds, MessageType type, String content, Long refId) {
         messageDAO.saveAll(
                 toUserIds.parallelStream()
                         .map(toUserId -> new Message()
                                 .setToUserId(toUserId)
                                 .setType(type)
                                 .setContent(content)
-                                .setSlideId(slideId))
+                                .setRefId(refId))
                         .collect(Collectors.toSet()));
     }
 
@@ -45,6 +45,12 @@ public class MessageServiceImpl implements MessageService {
         messageDAO.deleteByToUserIdAndIdIn(user.getId(), messageDTO.getIds());
     }
 
+    @Async
+    @Override
+    public void deleteMessages(Long refId, MessageType messageType) {
+        messageDAO.deleteByRefIdAndType(refId, messageType);
+    }
+
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void readMessages(LoginUser user, MessageDTO messageDTO) {

+ 43 - 21
src/main/java/nju/seec/helper/service/impl/NoticeServiceImpl.java

@@ -1,21 +1,20 @@
 package nju.seec.helper.service.impl;
 
+import nju.seec.helper.aspect.auth.LoginUser;
 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.dto.user.LoginUser;
 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.service.util.AuthUtils;
-import nju.seec.helper.service.util.StringUtils;
-import nju.seec.helper.util.enums.MessageType;
 import nju.seec.helper.vo.NoticeVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
-import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -43,15 +42,19 @@ public class NoticeServiceImpl implements NoticeService {
     @Override
     public NoticeVO createNotice(LoginUser user, NoticeDTO noticeDTO) {
         Course course = courseDAO.findCourseById(noticeDTO.getCourseId());
-        AuthUtils.checkDataAuth(user.getId(), course.getTeacher().getId(), "您无权创建该课程的公告");
+
         Notice notice = new Notice()
                 .setCourseId(noticeDTO.getCourseId())
                 .setTeacherId(user.getId())
                 .setTitle(noticeDTO.getTitle())
                 .setContent(noticeDTO.getContent());
 
+        if (!existsAddAuth(user, notice)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权创建该公告");
+        }
+
         Set<Long> studentIds = chooseDAO.findStudentIdsByCourseId(noticeDTO.getCourseId());
-        messageService.createMessage(studentIds, MessageType.NOTICE_NEW, String.format("[%s] 发布了新公告", course.getName()), null);
+        messageService.createMessage(studentIds, MessageType.NOTICE_NEW, String.format("[%s] 发布了新公告", course.getName()), course.getId());
         return new NoticeVO(noticeDAO.save(notice));
     }
 
@@ -59,24 +62,34 @@ public class NoticeServiceImpl implements NoticeService {
     @Override
     public NoticeVO modifyNotice(LoginUser user, Long noticeId, NoticeDTO noticeDTO) {
         Notice notice = noticeDAO.findNoticeById(noticeId);
-        AuthUtils.checkDataAuth(user.getId(), notice.getTeacherId(), "您无权修改该公告");
-        notice.setTitle(noticeDTO.getTitle())
-                .setContent(noticeDTO.getContent());
-        return new NoticeVO(noticeDAO.save(notice));
+
+        if (!existsUpdateAuth(user, notice)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权修改该公告");
+        }
+
+        return new NoticeVO(
+                noticeDAO.save(
+                        notice.setTitle(noticeDTO.getTitle())
+                                .setContent(noticeDTO.getContent())
+                )
+        );
     }
 
     @Transactional(readOnly = true)
     @Override
     public Page<NoticeVO> getNoticesByCourse(LoginUser user, Long courseId, String key, Pageable pageable) {
-        return findByCourseIdAndKey(courseId, key, pageable).map(NoticeVO::new);
+        return noticeDAO.findByCourseIdAndKey(courseId, key, pageable).map(NoticeVO::new);
     }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void deleteNotice(LoginUser user, Long noticeId) {
         Notice notice = noticeDAO.findNoticeById(noticeId);
-        AuthUtils.checkDataAuth(user.getId(), notice.getTeacherId(), "您无权删除该公告");
+        if (!existsDelAuth(user, notice)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权删除该公告");
+        }
         noticeDAO.delete(notice);
+        messageService.deleteMessages(noticeId, MessageType.NOTICE_NEW);
     }
 
     @Transactional(readOnly = true)
@@ -85,14 +98,23 @@ public class NoticeServiceImpl implements NoticeService {
         return new NoticeVO(noticeDAO.findNoticeById(noticeId));
     }
 
-    private Page<Notice> findByCourseIdAndKey(Long courseId, String key, Pageable pageable) {
-        String keyPattern = StringUtils.keyPattern(key);
+    @Override
+    public boolean existsAddAuth(LoginUser user, Notice notice) {
+        return courseDAO.findCourseById(notice.getCourseId()).getTeacher().getId().equals(user.getId());
+    }
 
-        return noticeDAO.findAll(
-                (Specification<Notice>) (root, query, cb) -> cb.and(
-                        cb.equal(root.get("courseId"), courseId)
-                        , cb.or(cb.like(root.get("title"), keyPattern), cb.like(root.get("content"), keyPattern))
-                ),
-                pageable);
+    @Override
+    public boolean existsDelAuth(LoginUser user, Notice notice) {
+        return notice.getTeacherId().equals(user.getId());
+    }
+
+    @Override
+    public boolean existsUpdateAuth(LoginUser user, Notice notice) {
+        return notice.getTeacherId().equals(user.getId());
+    }
+
+    @Override
+    public boolean existsGetAuth(LoginUser user, Notice notice) {
+        return true;
     }
 }

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

@@ -1,18 +1,18 @@
 package nju.seec.helper.service.impl;
 
+import nju.seec.helper.api.BokApi;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.QuestionRecordDAO;
 import nju.seec.helper.dao.QuizDAO;
 import nju.seec.helper.dao.UserDAO;
 import nju.seec.helper.dto.question.BaseQuestionDTO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.entity.QuestionRecord;
 import nju.seec.helper.entity.Quiz;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.enums.QuizState;
+import nju.seec.helper.enums.UserType;
+import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.service.QuestionService;
-import nju.seec.helper.util.BokUtil;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.enums.QuizState;
-import nju.seec.helper.util.enums.UserType;
-import nju.seec.helper.util.exception.HelperException;
 import nju.seec.helper.vo.question.BaseQuestionVO;
 import nju.seec.helper.vo.question.BokQuestion;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -38,21 +38,21 @@ public class QuestionServiceImpl implements QuestionService {
     private final UserDAO userDAO;
     private final QuestionRecordDAO questionRecordDAO;
 
-    private final BokUtil bokUtil;
+    private final BokApi bokApi;
 
     @Autowired
     public QuestionServiceImpl(QuizDAO quizDAO
             , UserDAO userDAO
-            , QuestionRecordDAO questionRecordDAO, BokUtil bokUtil) {
+            , QuestionRecordDAO questionRecordDAO, BokApi bokApi) {
         this.quizDAO = quizDAO;
         this.userDAO = userDAO;
         this.questionRecordDAO = questionRecordDAO;
-        this.bokUtil = bokUtil;
+        this.bokApi = bokApi;
     }
 
     @Override
     public Page<BaseQuestionVO> getQuestions(LoginUser user, String stem, Pageable pageable) {
-        return bokUtil.bokFindByStemLike(stem, pageable).map(bokQuestion -> BaseQuestionVO.convertBokQuestionToVO(bokQuestion, true));
+        return bokApi.bokFindByStemLike(stem, pageable).map(bokQuestion -> BaseQuestionVO.convertBokQuestionToVO(bokQuestion, true));
     }
 
     @Transactional(readOnly = true)
@@ -60,7 +60,7 @@ public class QuestionServiceImpl implements QuestionService {
     public List<BaseQuestionVO> getQuestionsByQuiz(LoginUser user, Long quizId) {
         Quiz quiz = quizDAO.findQuizById(quizId);
         List<String> questionIds = quiz.getQuestions();
-        return bokUtil.bokFindByIdIn(questionIds)
+        return bokApi.bokFindByIdIn(questionIds)
                 .stream()
                 .map(bokQuestion -> BaseQuestionVO.convertBokQuestionToVO(bokQuestion, quiz.getState() == QuizState.CLOSED || user.getType() == UserType.TEACHER))
                 .collect(Collectors.toList());
@@ -70,7 +70,7 @@ public class QuestionServiceImpl implements QuestionService {
     @Override
     public List<BaseQuestionVO> getQuestionsByQuiz(Quiz quiz) {
         List<String> questionIds = quiz.getQuestions();
-        return bokUtil.bokFindByIdIn(questionIds)
+        return bokApi.bokFindByIdIn(questionIds)
                 .stream()
                 .map(bokQuestion -> BaseQuestionVO.convertBokQuestionToVO(bokQuestion, true))
                 .collect(Collectors.toList());
@@ -78,7 +78,7 @@ public class QuestionServiceImpl implements QuestionService {
 
     @Override
     public BaseQuestionVO getOneQuestion(LoginUser user, String questionId) {
-        return BaseQuestionVO.convertBokQuestionToVO(bokUtil.bokFindById(questionId), true);
+        return BaseQuestionVO.convertBokQuestionToVO(bokApi.bokFindById(questionId), true);
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -90,11 +90,10 @@ public class QuestionServiceImpl implements QuestionService {
             questionId = UUID.randomUUID().toString().replaceAll("-", "");
         } while (questionRecordDAO.existsByQuestionId(questionId));
 
-        BokQuestion bokQuestion = bokUtil.createQuestion(questionId, baseQuestionDTO);
-        QuestionRecord questionRecord = new QuestionRecord()
+        BokQuestion bokQuestion = bokApi.createQuestion(questionId, baseQuestionDTO);
+        questionRecordDAO.save(new QuestionRecord()
                 .setTeacher(userDAO.findUserById(user.getId()))
-                .setQuestionId(bokQuestion.getId());
-        questionRecordDAO.save(questionRecord);
+                .setQuestionId(bokQuestion.getId()));
         return BaseQuestionVO.convertBokQuestionToVO(bokQuestion, true);
     }
 
@@ -103,7 +102,7 @@ public class QuestionServiceImpl implements QuestionService {
     public BaseQuestionVO modifyQuestion(LoginUser user, String questionId, BaseQuestionDTO baseQuestionDTO) {
         QuestionRecord questionRecord = questionRecordDAO.findByTeacherAndQuestionId(userDAO.findUserById(user.getId()), questionId)
                 .orElseThrow(() -> HelperException.of(ExceptionType.FORBIDDEN, "您无权修改该问题"));
-        BokQuestion bokQuestion = bokUtil.modifyQuestion(questionId, baseQuestionDTO);
+        BokQuestion bokQuestion = bokApi.modifyQuestion(questionId, baseQuestionDTO);
         questionRecordDAO.save(questionRecord);
         return BaseQuestionVO.convertBokQuestionToVO(bokQuestion, true);
     }
@@ -113,18 +112,17 @@ public class QuestionServiceImpl implements QuestionService {
     public void deleteQuestion(LoginUser user, String questionId) {
         QuestionRecord questionRecord = questionRecordDAO.findByTeacherAndQuestionId(userDAO.findUserById(user.getId()), questionId)
                 .orElseThrow(() -> HelperException.of(ExceptionType.FORBIDDEN, "您无权删除该问题"));
-        bokUtil.deleteQuestion(questionId);
+        bokApi.deleteQuestion(questionId);
         questionRecordDAO.delete(questionRecord);
     }
 
     @Transactional(readOnly = true)
     @Override
     public Page<BaseQuestionVO> getCreatedQuestions(LoginUser user, Pageable pageable) {
-        Page<String> questionIdPage = questionRecordDAO
-                .findQuestionIdsByTeacher(userDAO.findUserById(user.getId()), pageable);
+        Page<String> questionIdPage = questionRecordDAO.findQuestionIdsByTeacher(userDAO.findUserById(user.getId()), pageable);
 
         return new PageImpl<>(
-                bokUtil.bokFindByIdIn(questionIdPage.getContent())
+                bokApi.bokFindByIdIn(questionIdPage.getContent())
                         .stream()
                         .map(bokQuestion -> BaseQuestionVO.convertBokQuestionToVO(bokQuestion, true))
                         .collect(Collectors.toList())

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

@@ -1,27 +1,30 @@
 package nju.seec.helper.service.impl;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import nju.seec.helper.dao.*;
+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.dto.quiz.QuizDTO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.entity.Course;
 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.service.util.AuthUtils;
-import nju.seec.helper.service.util.StringUtils;
-import nju.seec.helper.util.enums.*;
-import nju.seec.helper.util.exception.HelperException;
 import nju.seec.helper.vo.quiz.QuizVO;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
-import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 
@@ -36,42 +39,72 @@ public class QuizServiceImpl implements QuizService {
     private final QuizDAO quizDAO;
     private final QuizStudentAnswerDAO quizStudentAnswerDAO;
     private final ChooseDAO chooseDAO;
-    private final CourseDAO courseDAO;
-    private final UserDAO userDAO;
 
     private final MessageService messageService;
 
     @Autowired
-    public QuizServiceImpl(SlideDAO slideDAO, QuizDAO quizDAO, QuizStudentAnswerDAO quizStudentAnswerDAO, ChooseDAO chooseDAO, CourseDAO courseDAO, UserDAO userDAO, MessageService messageService) {
+    public QuizServiceImpl(SlideDAO slideDAO, QuizDAO quizDAO, QuizStudentAnswerDAO quizStudentAnswerDAO, ChooseDAO chooseDAO, MessageService messageService) {
         this.slideDAO = slideDAO;
         this.quizDAO = quizDAO;
         this.quizStudentAnswerDAO = quizStudentAnswerDAO;
         this.chooseDAO = chooseDAO;
-        this.courseDAO = courseDAO;
-        this.userDAO = userDAO;
         this.messageService = messageService;
     }
 
+    private static Map<SlideState, Map<QuizType, QuizState>> MAP = ImmutableMap.of(
+            SlideState.DRAFT, ImmutableMap.of(
+                    QuizType.BEFORE_CLASS, QuizState.NOT_STARTED,
+                    QuizType.IN_CLASS, QuizState.NOT_STARTED,
+                    QuizType.AFTER_CLASS, QuizState.NOT_STARTED
+            ),
+            SlideState.BEFORE_CLASS, ImmutableMap.of(
+                    QuizType.BEFORE_CLASS, QuizState.ONGOING,
+                    QuizType.IN_CLASS, QuizState.NOT_STARTED,
+                    QuizType.AFTER_CLASS, QuizState.NOT_STARTED
+            ),
+            SlideState.IN_CLASS, ImmutableMap.of(
+                    QuizType.BEFORE_CLASS, QuizState.CLOSED,
+                    QuizType.IN_CLASS, QuizState.ONGOING,
+                    QuizType.AFTER_CLASS, QuizState.NOT_STARTED
+            ),
+            SlideState.AFTER_CLASS, ImmutableMap.of(
+                    QuizType.BEFORE_CLASS, QuizState.CLOSED,
+                    QuizType.IN_CLASS, QuizState.CLOSED,
+                    QuizType.AFTER_CLASS, QuizState.ONGOING
+            ),
+            SlideState.FINISH, ImmutableMap.of(
+                    QuizType.BEFORE_CLASS, QuizState.CLOSED,
+                    QuizType.IN_CLASS, QuizState.CLOSED,
+                    QuizType.AFTER_CLASS, QuizState.CLOSED
+            )
+    );
+
     @Transactional(rollbackFor = Exception.class)
     @Override
     public QuizVO createQuiz(LoginUser user, QuizDTO quizDTO) {
         Slide slide = slideDAO.findSlideById(quizDTO.getSlideId());
 
-        AuthUtils.checkDataAuth(user.getId(), slide.getTeacher().getId(), "您无权创建该课件的测试");
-        checkSameName(0L, quizDTO.getSlideId(), quizDTO.getName());
-        checkQuizTypeOkToSlideState(slide.getState(), quizDTO.getType());
-
         Quiz quiz = new Quiz()
                 .setCourse(slide.getCourse())
                 .setSlide(slide)
                 .setTeacher(slide.getTeacher())
                 .setType(quizDTO.getType())
-                .setState(QuizState.getQuizStateBySlideStateAndQuizType(slide.getState(), quizDTO.getType()))
+                .setState(MAP.get(slide.getState()).get(quizDTO.getType()))
                 .setName(quizDTO.getName())
                 .setMaxSubmitNumber(quizDTO.getMaxSubmitTime())
                 .setQuestions(quizDTO.getQuestions());
 
-        return new QuizVO(quizDAO.save(quiz));
+        if (!existsAddAuth(user, quiz)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权创建该测试");
+        }
+
+        try {
+            quiz = quizDAO.save(quiz);
+            sendMessages(quiz);
+            return new QuizVO(quiz);
+        } catch (DataIntegrityViolationException e) {
+            throw HelperException.of(ExceptionType.CONFLICT, "创建失败,请检查测试信息是否正确且不与已有测试冲突");
+        }
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -79,67 +112,52 @@ public class QuizServiceImpl implements QuizService {
     public QuizVO modifyQuiz(LoginUser user, Long quizId, QuizDTO quizDTO) {
         Quiz quiz = quizDAO.findQuizById(quizId);
 
-        AuthUtils.checkDataAuth(user.getId(), quiz.getTeacher().getId(), "您无权修改该测试");
-        checkSubmit(quiz);
-
-        Slide slide = quiz.getSlide();
-        checkQuizTypeOkToSlideState(slide.getState(), quizDTO.getType());
-
-        checkSameName(quiz.getId(), quizDTO.getSlideId(), quizDTO.getName());
-
-        quiz.setType(quizDTO.getType())
-                .setState(QuizState.getQuizStateBySlideStateAndQuizType(slide.getState(), quizDTO.getType()))
-                .setName(quizDTO.getName())
-                .setMaxSubmitNumber(quizDTO.getMaxSubmitTime())
-                .setQuestions(quizDTO.getQuestions());
-
-        return new QuizVO(quizDAO.save(quiz));
-    }
-
-    private void checkSameName(Long quizId, Long slideId, String name) {
-        long exists = quizDAO.count((Specification<Quiz>) (root, query, cb) ->
-                cb.and(cb.notEqual(root.get("id"), quizId),
-                        cb.equal(root.get("slide").get("id"), slideId),
-                        cb.equal(root.get("name"), name)
-                ));
-        if (exists > 0) {
-            throw HelperException.of(ExceptionType.CONFLICT, "该测试名已使用");
+        if (existsUpdateAuth(user, quiz)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权修改该测试");
         }
-    }
 
-    private void checkQuizTypeOkToSlideState(SlideState slideState, QuizType quizType) {
-        if (!QuizType.getQuizTypesBySlideState(slideState).contains(quizType)) {
-            throw HelperException.of(ExceptionType.FORBIDDEN, "课件暂时不支持创建该类型的测试,请检查课件状态");
-        }
-    }
-
-    private void checkSubmit(Quiz quiz) {
         if (quizStudentAnswerDAO.existsByQuiz(quiz)) {
             throw HelperException.of(ExceptionType.FORBIDDEN, "该测试已有提交,无法修改");
         }
+
+        try {
+            quiz = quizDAO.save(quiz
+                    .setType(quizDTO.getType())
+                    .setState(MAP.get(quiz.getSlide().getState()).get(quizDTO.getType()))
+                    .setName(quizDTO.getName())
+                    .setMaxSubmitNumber(quizDTO.getMaxSubmitTime())
+                    .setQuestions(quizDTO.getQuestions()));
+            sendMessages(quiz);
+            return new QuizVO(quiz);
+        } catch (DataIntegrityViolationException e) {
+            throw HelperException.of(ExceptionType.CONFLICT, "修改失败,请检查测试信息是否正确且不与已有测试冲突");
+        }
     }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void deleteQuiz(LoginUser user, Long quizId) {
         Quiz quiz = quizDAO.findQuizById(quizId);
-        AuthUtils.checkDataAuth(user.getId(), quiz.getTeacher().getId(), "您无权删除该测试");
+        if (!existsDelAuth(user, quiz)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权删除该测试");
+        }
         quiz.setDeleteAt(System.currentTimeMillis());
         quizDAO.save(quiz);
+        messageService.deleteMessages(quizId, MessageType.QUIZ_NEW);
     }
 
+    private static final Set<QuizState> STATES_NOT_GET_BY_STUDENT_SET = ImmutableSet.of(QuizState.NOT_STARTED);
+
     @Transactional(readOnly = true)
     @Override
     public Page<QuizVO> teacherGetQuizzesBySlide(LoginUser user, Long slideId, String key, Pageable pageable) {
-        return findBySlideIdAndKey(slideId, key, pageable).map(QuizVO::new);
+        return quizDAO.findBySlideAndNameContains(slideDAO.findSlideById(slideId), key, pageable).map(QuizVO::new);
     }
 
-    public static final Set<QuizState> STATES_NOT_GET_BY_STUDENT_SET = ImmutableSet.of(QuizState.NOT_STARTED);
-
     @Transactional(readOnly = true)
     @Override
     public Page<QuizVO> studentGetQuizzesBySlide(LoginUser user, Long slideId, String key, Pageable pageable) {
-        return findBySlideIdAndStateNotInAndKey(slideId, STATES_NOT_GET_BY_STUDENT_SET, key, pageable).map(QuizVO::new);
+        return quizDAO.findBySlideAndStateNotInAndNameContains(slideDAO.findSlideById(slideId), STATES_NOT_GET_BY_STUDENT_SET, key, pageable).map(QuizVO::new);
     }
 
     @Transactional(readOnly = true)
@@ -149,84 +167,56 @@ public class QuizServiceImpl implements QuizService {
             throw HelperException.of(ExceptionType.FORBIDDEN, "您暂时无法获取该状态的测试");
         }
         Set<Long> courseIds = chooseDAO.findCourseIdsByStudentId(user.getId());
-        return findByCourseIdInAndStateAndKey(courseIds, state, key, pageable).map(QuizVO::new);
+        return quizDAO.findByCourseIdsAndStateAndNameContains(courseIds, state, key, pageable).map(QuizVO::new);
     }
 
     @Transactional(readOnly = true)
     @Override
     public QuizVO getOneQuiz(LoginUser user, Long quizId) {
         Quiz quiz = quizDAO.findQuizById(quizId);
-        checkDataAccessAuth(user, quiz);
+        if (!existsGetAuth(user, quiz)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权查看该测试");
+        }
         return new QuizVO(quiz);
     }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void modifyQuizState(Slide slide) {
-        List<Quiz> quizzes = quizDAO.findAllBySlide(slide);
-        Course course = slide.getCourse();
-        Set<Long> chooseStudentIds = chooseDAO.findStudentIdsByCourseId(course.getId());
+        List<Quiz> quizzes = quizDAO.findBySlide(slide);
         quizzes.forEach(quiz -> {
-            QuizState quizState = QuizState.getQuizStateBySlideStateAndQuizType(slide.getState(), quiz.getType());
-            quiz.setState(quizState);
-            if (quizState == QuizState.ONGOING) {
-                messageService.createMessage(chooseStudentIds, MessageType.QUIZ_NEW, String.format("[%s] - [%s] 的测试 [%s] 已开放", course.getName(), slide.getName(), quiz.getName()), slide.getId());
-            }
+            quiz.setState(MAP.get(slide.getState()).get(quiz.getType()));
+            sendMessages(quiz);
         });
     }
 
-    private void checkDataAccessAuth(LoginUser user, Quiz quiz) {
-        HelperException forbiddenEx = HelperException.of(ExceptionType.FORBIDDEN, "您无权访问该测试");
-
-        switch (user.getType()) {
-            case TEACHER:
-                break;
-            case STUDENT:
-                if (quiz.getState() == QuizState.NOT_STARTED) {
-                    throw forbiddenEx;
-                }
-                break;
-            default:
-                throw forbiddenEx;
+    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) {
+            messageService.createMessage(chooseStudentIds, MessageType.QUIZ_NEW, String.format("[%s] - [%s] 的测试 [%s] 已开放", course.getName(), slide.getName(), quiz.getName()), quiz.getId());
         }
     }
 
-    private Page<Quiz> findByCourseIdInAndStateAndKey(Set<Long> courseIds, QuizState state, String key, Pageable pageable) {
-        return quizDAO.findAll(
-                (Specification<Quiz>) (root, query, cb) -> cb.and(
-                        root.get("course").get("id").in(courseIds)
-                        , cb.equal(root.get("deleteAt"), 0L)
-                        , cb.equal(root.get("state"), state)
-                        , cb.like(root.get("name"), StringUtils.keyPattern(key))
-                        , cb.equal(root.get("slide").get("deleteAt"), 0L)
-                        , cb.equal(root.get("course").get("deleteAt"), 0L)
-                ),
-                pageable);
+    @Override
+    public boolean existsAddAuth(LoginUser user, Quiz quiz) {
+        return quiz.getCourse().getTeacher().getId().equals(user.getId());
     }
 
-    private Page<Quiz> findBySlideIdAndKey(Long slideId, String key, Pageable pageable) {
-        return quizDAO.findAll(
-                (Specification<Quiz>) (root, query, cb) -> cb.and(
-                        cb.equal(root.get("slide").get("id"), slideId)
-                        , cb.equal(root.get("deleteAt"), 0L)
-                        , cb.like(root.get("name"), StringUtils.keyPattern(key))
-                        , cb.equal(root.get("slide").get("deleteAt"), 0L)
-                        , cb.equal(root.get("course").get("deleteAt"), 0L)
-                ),
-                pageable);
+    @Override
+    public boolean existsDelAuth(LoginUser user, Quiz quiz) {
+        return quiz.getTeacher().getId().equals(user.getId());
     }
 
+    @Override
+    public boolean existsUpdateAuth(LoginUser user, Quiz quiz) {
+        return quiz.getTeacher().getId().equals(user.getId());
+    }
 
-    private Page<Quiz> findBySlideIdAndStateNotInAndKey(Long slideId, Set<QuizState> exclusiveStates, String key, Pageable pageable) {
-        return quizDAO.findAll(
-                (Specification<Quiz>) (root, query, cb) -> cb.and(
-                        cb.equal(root.get("slide").get("id"), slideId)
-                        , cb.equal(root.get("deleteAt"), 0L)
-                        , cb.like(root.get("name"), StringUtils.keyPattern(key))
-                        , cb.not(root.get("state").in(exclusiveStates))
-                        , cb.equal(root.get("slide").get("deleteAt"), 0L)
-                        , cb.equal(root.get("course").get("deleteAt"), 0L)
-                ),
-                pageable);
+    @Override
+    public boolean existsGetAuth(LoginUser user, Quiz quiz) {
+        return quiz.getState() != QuizState.NOT_STARTED
+                || user.getType() == UserType.TEACHER;
     }
 }

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

@@ -1,17 +1,19 @@
 package nju.seec.helper.service.impl;
 
+import com.google.common.collect.Maps;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.QuizDAO;
 import nju.seec.helper.dao.QuizStudentAnswerDAO;
 import nju.seec.helper.dao.UserDAO;
 import nju.seec.helper.dto.quiz.QuizStudentAnswerDTO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.entity.Quiz;
 import nju.seec.helper.entity.QuizStudentAnswer;
 import nju.seec.helper.entity.User;
+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.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.vo.QuizStudentAnswerStatisticVO;
 import nju.seec.helper.vo.question.BaseQuestionVO;
 import nju.seec.helper.vo.quiz.QuizStudentAnswerVO;
 import org.springframework.data.domain.Page;
@@ -93,17 +95,34 @@ public class QuizStudentAnswerServiceImpl implements QuizStudentAnswerService {
 
     @Transactional(readOnly = true)
     @Override
-    public Page<QuizStudentAnswerVO> teacherGetQuizStudentAnswers(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, Pageable pageable) {
+        return quizStudentAnswerDAO.findByQuiz(quizDAO.findQuizById(quizId), pageable).map(quizStudentAnswer -> new QuizStudentAnswerVO(quizStudentAnswer, questionService.getQuestionsByQuiz(quizDAO.findQuizById(quizId))));
     }
 
     @Transactional(readOnly = true)
     @Override
-    public QuizStudentAnswerVO studentGetSelfQuizStudentAnswer(LoginUser user, Long quizId) {
+    public QuizStudentAnswerVO getQuizStudentAnswer(LoginUser user, Long quizId) {
         return quizStudentAnswerDAO
                 .findByQuizAndStudent(quizDAO.findQuizById(quizId), userDAO.findUserById(user.getId()))
                 .map(quizStudentAnswer -> new QuizStudentAnswerVO(quizStudentAnswer, questionService.getQuestionsByQuiz(quizDAO.findQuizById(quizId))))
                 .orElse(null);
     }
+
+    @Transactional(readOnly = true)
+    @Override
+    public QuizStudentAnswerStatisticVO getQuizStudentAnswerStatistic(LoginUser user, Long quizId) {
+        List<QuizStudentAnswer> quizStudentAnswers = quizStudentAnswerDAO.findByQuiz(quizDAO.findQuizById(quizId));
+
+        Map<BigDecimal, Integer> scores = Maps.newHashMapWithExpectedSize(quizStudentAnswers.size());
+        Map<Integer, Integer> submitNums = Maps.newHashMapWithExpectedSize(quizStudentAnswers.size());
+
+        quizStudentAnswers.forEach(
+                quizStudentAnswer -> {
+                    scores.merge(quizStudentAnswer.getScore(), 1, Integer::sum);
+                    submitNums.merge(quizStudentAnswer.getSubmitNumber(), 1, Integer::sum);
+                }
+        );
+
+        return QuizStudentAnswerStatisticVO.of(quizId, scores, submitNums);
+    }
 }

+ 37 - 12
src/main/java/nju/seec/helper/service/impl/ReplyServiceImpl.java

@@ -1,21 +1,21 @@
 package nju.seec.helper.service.impl;
 
 import lombok.extern.slf4j.Slf4j;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.CommentDAO;
 import nju.seec.helper.dao.ReplyDAO;
 import nju.seec.helper.dao.SlideDAO;
 import nju.seec.helper.dao.UserDAO;
 import nju.seec.helper.dto.reply.ReplyDTO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.entity.Comment;
 import nju.seec.helper.entity.Reply;
 import nju.seec.helper.entity.Slide;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.enums.MessageType;
+import nju.seec.helper.enums.UserType;
+import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.service.MessageService;
 import nju.seec.helper.service.ReplyService;
-import nju.seec.helper.service.util.AuthUtils;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.enums.MessageType;
-import nju.seec.helper.util.exception.HelperException;
 import nju.seec.helper.vo.ReplyVO;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -47,17 +47,19 @@ public class ReplyServiceImpl implements ReplyService {
     @Override
     public ReplyVO createReply(LoginUser user, ReplyDTO replyDTO) {
         Comment comment = commentDAO.findCommentById(replyDTO.getCommentId());
-        AuthUtils.checkDataAuth(user.getId(), slideDAO.findSlideById(comment.getSlideId()).getTeacher().getId(), "您无权回复该评论");
 
-        if (comment.getReply() != null) {
-            throw HelperException.of(ExceptionType.CONFLICT, "该评论已回复");
-        }
-        Reply reply = new Reply()
+        Reply reply = (comment.getReply() != null ?
+                comment.getReply() :
+                new Reply())
                 .setComment(comment)
                 .setTeacher(userDAO.findUserById(user.getId()))
                 .setContent(replyDTO.getContent());
+
+        if (!existsAddAuth(user, reply)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权回复该评论");
+        }
+
         comment.setReply(reply);
-        reply.setComment(comment);
         reply = replyDAO.save(reply);
 
         // 增加通知
@@ -73,10 +75,33 @@ public class ReplyServiceImpl implements ReplyService {
     @Override
     public void deleteReply(LoginUser user, Long replyId) {
         Reply reply = replyDAO.findReplyById(replyId);
-        AuthUtils.checkDataAuth(user.getId(), reply.getTeacher().getId(), "您无权删除该评论");
+
+        if (!existsDelAuth(user, reply)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权删除该评论");
+        }
 
         Comment comment = reply.getComment();
         comment.setReply(null);
         replyDAO.delete(reply);
     }
+
+    @Override
+    public boolean existsAddAuth(LoginUser user, Reply reply) {
+        return user.getType() == UserType.TEACHER;
+    }
+
+    @Override
+    public boolean existsDelAuth(LoginUser user, Reply reply) {
+        return reply.getTeacher().getId().equals(user.getId());
+    }
+
+    @Override
+    public boolean existsUpdateAuth(LoginUser user, Reply reply) {
+        return reply.getTeacher().getId().equals(user.getId());
+    }
+
+    @Override
+    public boolean existsGetAuth(LoginUser user, Reply reply) {
+        return true;
+    }
 }

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

@@ -1,8 +1,9 @@
 package nju.seec.helper.service.impl;
 
 import com.google.common.collect.ImmutableSet;
-import lombok.AllArgsConstructor;
+import com.google.common.io.Files;
 import lombok.SneakyThrows;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.ChooseDAO;
 import nju.seec.helper.dao.CourseDAO;
 import nju.seec.helper.dao.SlideDAO;
@@ -10,35 +11,30 @@ import nju.seec.helper.dao.UserDAO;
 import nju.seec.helper.dto.slide.SlideDTO;
 import nju.seec.helper.dto.slide.SlideFileDTO;
 import nju.seec.helper.dto.slide.SlideStateDTO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.entity.Course;
 import nju.seec.helper.entity.Slide;
+import nju.seec.helper.enums.ExceptionType;
+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.service.util.AuthUtils;
-import nju.seec.helper.service.util.StringUtils;
-import nju.seec.helper.util.Consts;
-import nju.seec.helper.util.FileUtils;
-import nju.seec.helper.util.RedisCacheUtils;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.enums.MessageType;
-import nju.seec.helper.util.enums.SlideState;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.service.util.OssObjectUrlUtils;
+import nju.seec.helper.util.OssUtils;
+import nju.seec.helper.util.file.FileInfo;
+import nju.seec.helper.util.file.FileUtils;
 import nju.seec.helper.vo.SlideVO;
-import org.apache.pdfbox.pdmodel.PDDocument;
+import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
-import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.File;
-import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
 
 /**
  * @author cst
@@ -46,40 +42,37 @@ import java.util.concurrent.TimeUnit;
 @Service
 public class SlideServiceImpl implements SlideService {
     private static final String SLIDE_STORE_DIR = "slide";
-    private static final long SLIDE_URL_LIVING_SECONDS = 20 * 60;
 
     private final CourseDAO courseDAO;
     private final SlideDAO slideDAO;
     private final ChooseDAO chooseDAO;
     private final UserDAO userDAO;
 
-    private final QuizService quizService;
-    private final FileUtils fileUtils;
-    private final RedisCacheUtils cacheUtils;
+    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, FileUtils fileUtils, RedisCacheUtils cacheUtils, MessageService messageService, QuizService quizService, CourseService courseService) {
+    public SlideServiceImpl(CourseDAO courseDAO, SlideDAO slideDAO, ChooseDAO chooseDAO, UserDAO userDAO, OssUtils ossUtils, OssObjectUrlUtils ossObjectUrlUtils, QuizService quizService, MessageService messageService, CourseService courseService) {
         this.courseDAO = courseDAO;
         this.slideDAO = slideDAO;
         this.chooseDAO = chooseDAO;
         this.userDAO = userDAO;
-        this.fileUtils = fileUtils;
-        this.cacheUtils = cacheUtils;
-        this.messageService = messageService;
+        this.ossUtils = ossUtils;
+        this.ossObjectUrlUtils = ossObjectUrlUtils;
         this.quizService = quizService;
+        this.messageService = messageService;
         this.courseService = courseService;
     }
 
+
     @Transactional(rollbackFor = Exception.class)
     @SneakyThrows
     @Override
     public SlideVO createSlide(LoginUser teacher, SlideDTO slideDTO) {
         Course course = courseDAO.findCourseById(slideDTO.getCourseId());
-        AuthUtils.checkDataAuth(teacher.getId(), course.getTeacher().getId(), "您无权创建该课程的课件");
-
-        checkSameName(0L, slideDTO.getCourseId(), slideDTO.getName());
 
         Slide slide = new Slide()
                 .setCourse(course)
@@ -87,52 +80,53 @@ public class SlideServiceImpl implements SlideService {
                 .setName(slideDTO.getName())
                 .setState(slideDTO.getState());
 
-        MultipartFile file = slideDTO.getFile();
-        FileInfo fileInfo = getFileInfo(file);
-        slide.setPages(fileInfo.pages);
-        slide.setObjectName("temp");
-        slide = slideDAO.save(slide);
+        if (!existsAddAuth(teacher, slide)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权创建该课件");
+        }
 
-        String objectName = getObjectName(slide.getId(), slide.getName(), fileInfo.suffix);
-        slide.setObjectName(objectName);
-        slide = slideDAO.save(slide);
+        try {
+            MultipartFile file = slideDTO.getFile();
+            FileInfo fileInfo = getFileInfo(file);
+            slide.setPages(fileInfo.getPages());
+            slide.setObjectName("temp");
+            slide = slideDAO.save(slide);
 
-        fileUtils.upload(objectName, file.getInputStream());
+            String objectName = getObjectName(slide.getId(), slide.getName(), fileInfo.getExtension());
+            slide.setObjectName(objectName);
+            slide = slideDAO.save(slide);
 
-        return new SlideVO(slide);
+            ossUtils.upload(objectName, file.getInputStream());
+
+            return new SlideVO(slide);
+        } catch (DataIntegrityViolationException e) {
+            throw HelperException.of(ExceptionType.CONFLICT, "创建失败,请检查课件信息是否正确且不与已有课件冲突");
+        }
     }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
     public SlideVO modifySlide(LoginUser user, Long slideId, SlideDTO slideDTO) {
         Slide slide = slideDAO.findSlideById(slideId);
-        AuthUtils.checkDataAuth(user.getId(), slide.getTeacher().getId(), "您无权修改该课件");
-
-        checkSameName(slide.getId(), slide.getCourse().getId(), slideDTO.getName());
 
-        slide.setName(slideDTO.getName());
-        String oldObjectName = slide.getObjectName();
-        String newObjectName = getObjectName(slide.getId(), slide.getName(), oldObjectName.substring(oldObjectName.lastIndexOf('.') + 1));
-        slide.setObjectName(newObjectName);
-        slide = slideDAO.save(slide);
-
-        if (!oldObjectName.equals(newObjectName)) {
-            fileUtils.copy(oldObjectName, newObjectName);
-            fileUtils.delete(oldObjectName);
+        if (!existsUpdateAuth(user, slide)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权修改该课件");
         }
 
-        return new SlideVO(slide);
-    }
-
-    private void checkSameName(Long slideId, Long courseId, String name) {
-        long exists = slideDAO.count((Specification<Slide>) (root, query, cb) -> cb.and(
-                cb.notEqual(root.get("id"), slideId),
-                cb.equal(root.get("course").get("id"), courseId),
-                cb.equal(root.get("deleteAt"), 0L),
-                cb.equal(root.get("name"), name)));
-
-        if (exists > 0) {
-            throw HelperException.of(ExceptionType.CONFLICT, "该课件名已使用");
+        try {
+            slide.setName(slideDTO.getName());
+            String oldObjectName = slide.getObjectName();
+            String newObjectName = getObjectName(slide.getId(), slide.getName(), Files.getFileExtension(oldObjectName));
+            slide.setObjectName(newObjectName);
+            slide = slideDAO.save(slide);
+
+            if (!oldObjectName.equals(newObjectName)) {
+                ossUtils.copy(oldObjectName, newObjectName);
+                ossUtils.delete(oldObjectName);
+            }
+
+            return new SlideVO(slide);
+        } catch (DataIntegrityViolationException e) {
+            throw HelperException.of(ExceptionType.CONFLICT, "修改失败,请检查课件信息是否正确且不与已有课件冲突");
         }
     }
 
@@ -140,7 +134,10 @@ public class SlideServiceImpl implements SlideService {
     @Override
     public void modifySlideState(LoginUser user, Long slideId, SlideStateDTO slideStateDTO) {
         Slide slide = slideDAO.findSlideById(slideId);
-        AuthUtils.checkDataAuth(user.getId(), slide.getTeacher().getId(), "您无权修改该课件状态");
+
+        if (!existsUpdateAuth(user, slide)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权修改该课件状态");
+        }
 
         slide.setState(slideStateDTO.getState());
         slide = slideDAO.save(slide);
@@ -148,7 +145,7 @@ public class SlideServiceImpl implements SlideService {
         // 增加通知
         if (slide.getState() == SlideState.IN_CLASS) {
             Set<Long> studentIds = chooseDAO.findStudentIdsByCourseId(slide.getCourse().getId());
-            messageService.createMessage(studentIds, MessageType.SLIDE_IN_CLASS, String.format(" [%s] - [%s] 已开课", slide.getCourse().getName(), slide.getName()), slide.getId());
+            messageService.createMessage(studentIds, MessageType.SLIDE_IN_CLASS, String.format("[%s] - [%s] 已开课", slide.getCourse().getName(), slide.getName()), slide.getId());
         }
 
         // 修改Slide状态同时推进quiz状态
@@ -160,20 +157,23 @@ public class SlideServiceImpl implements SlideService {
     @Override
     public void modifySlideFile(LoginUser user, Long slideId, SlideFileDTO slideFileDTO) {
         Slide slide = slideDAO.findSlideById(slideId);
-        AuthUtils.checkDataAuth(user.getId(), slide.getTeacher().getId(), "您无权修改该课件文件");
+
+        if (!existsUpdateAuth(user, slide)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权修改该课件文件");
+        }
 
         MultipartFile file = slideFileDTO.getFile();
         FileInfo fileInfo = getFileInfo(file);
-        slide.setPages(fileInfo.pages);
+        slide.setPages(fileInfo.getPages());
 
         String oldObjectName = slide.getObjectName();
-        String newObjectName = getObjectName(slide.getId(), slide.getName(), fileInfo.suffix);
+        String newObjectName = getObjectName(slide.getId(), slide.getName(), fileInfo.getExtension());
         slide.setObjectName(newObjectName);
         slideDAO.save(slide);
 
-        fileUtils.upload(newObjectName, file.getInputStream());
+        ossUtils.upload(newObjectName, file.getInputStream());
         if (!oldObjectName.equals(newObjectName)) {
-            fileUtils.delete(oldObjectName);
+            ossUtils.delete(oldObjectName);
         }
     }
 
@@ -181,125 +181,93 @@ public class SlideServiceImpl implements SlideService {
     @Override
     public void deleteSlide(LoginUser user, Long slideId) {
         Slide slide = slideDAO.findSlideById(slideId);
-        AuthUtils.checkDataAuth(user.getId(), slide.getTeacher().getId(), "您无权删除该课件");
+
+        if (!existsDelAuth(user, slide)) {
+            throw HelperException.of(ExceptionType.FORBIDDEN, "您无权删除该课件");
+        }
 
         slide.setDeleteAt(System.currentTimeMillis());
         slideDAO.save(slide);
+        messageService.deleteMessages(slideId, MessageType.SLIDE_IN_CLASS);
     }
 
-    @SuppressWarnings("all")
     @SneakyThrows
     private FileInfo getFileInfo(MultipartFile file) {
-        switch (Objects.requireNonNull(file.getContentType())) {
-            case "application/pdf":
-                // 解析页数
-                PDDocument doc = PDDocument.load(file.getInputStream());
-                return FileInfo.of(doc.getNumberOfPages(), "pdf");
-            default:
-                throw HelperException.of(ExceptionType.PARAM_ERROR, "不支持的文件类型");
-        }
+        return FileUtils.getFileInfo(file);
     }
 
-    @AllArgsConstructor(staticName = "of")
-    private static class FileInfo {
-        private int pages;
-        private String suffix;
-    }
-
-    private String getObjectName(Long slideId, String slideName, String suffix) {
-        return SLIDE_STORE_DIR + File.separator + slideId + File.separator + slideName + "." + suffix;
+    private String getObjectName(Long slideId, String slideName, String extension) {
+        return SLIDE_STORE_DIR + File.separator + slideId + File.separator + slideName + "." + extension;
     }
 
     @Transactional(readOnly = true)
     @Override
     public Page<SlideVO> teacherGetSlides(LoginUser teacher, String key, Pageable pageable) {
-        return findByTeacherIdAndKey(teacher.getId(), key, pageable).map(SlideVO::new);
+        return slideDAO.findByTeacherAndNameContains(userDAO.findUserById(teacher.getId()), key, pageable).map(SlideVO::new);
     }
 
+    private static final Set<SlideState> STATES_NOT_GET_BY_STUDENT_SET = ImmutableSet.of(SlideState.DRAFT);
+
     @Transactional(readOnly = true)
     @Override
     public Page<SlideVO> getSlidesByCourse(LoginUser user, Long courseId, String key, Pageable pageable) {
-        if (!courseService.existsCourseResourcesGetAuth(user, courseId)) {
+        Course course = courseDAO.findCourseById(courseId);
+
+        if (!courseService.existsGetAuth(user, course)) {
             throw HelperException.of(ExceptionType.FORBIDDEN, "您无权访问该课程的课件");
         }
+
         switch (user.getType()) {
             case TEACHER:
-                return teacherGetSlidesByCourse(courseId, key, pageable);
+                return slideDAO.findByCourseAndNameContains(course, key, pageable).map(SlideVO::new);
             case STUDENT:
-                return studentGetSlidesByCourse(courseId, key, pageable);
+                return slideDAO.findByCourseAndStateNotInAndNameContains(course, STATES_NOT_GET_BY_STUDENT_SET, key, pageable).map(SlideVO::new);
             default:
                 return Page.empty();
         }
     }
 
-    private Page<SlideVO> teacherGetSlidesByCourse(Long courseId, String key, Pageable pageable) {
-        return findByCourseIdAndKey(courseId, key, pageable).map(SlideVO::new);
-    }
-
-    private static final Set<SlideState> STATES_NOT_GET_BY_STUDENT_SET = ImmutableSet.of(SlideState.DRAFT);
-
-    private Page<SlideVO> studentGetSlidesByCourse(Long courseId, String key, Pageable pageable) {
-        return findByCourseIdAndKeyAndStateNotIn(courseId, STATES_NOT_GET_BY_STUDENT_SET, key, pageable).map(SlideVO::new);
-    }
-
     @Transactional(readOnly = true)
     @Override
     public String getSlideUrl(LoginUser user, Long slideId) {
         Slide slide = slideDAO.findSlideById(slideId);
-        if (!courseService.existsCourseResourcesGetAuth(user, slide.getCourse().getId())) {
+
+        if (!existsGetAuth(user, slide)) {
             throw HelperException.of(ExceptionType.FORBIDDEN, "您无权获取该课件的链接");
         }
 
-        String url = cacheUtils.get(Consts.SLIDE_URL_CACHE_NAME, slide.getObjectName());
-        if (url != null) {
-            return url;
-        }
-        url = fileUtils.getUrl(slide.getObjectName(), SLIDE_URL_LIVING_SECONDS);
-        cacheUtils.set(Consts.SLIDE_URL_CACHE_NAME, slide.getObjectName(), url, SLIDE_URL_LIVING_SECONDS, TimeUnit.SECONDS);
-        return url;
+        return ossObjectUrlUtils.getUrl(slide.getObjectName());
     }
 
     @Transactional(readOnly = true)
     @Override
     public SlideVO getOneSlide(LoginUser user, Long slideId) {
         Slide slide = slideDAO.findSlideById(slideId);
-        if (!courseService.existsCourseResourcesGetAuth(user, slide.getCourse().getId())) {
+
+        if (!existsGetAuth(user, slide)) {
             throw HelperException.of(ExceptionType.FORBIDDEN, "您无权访问该课件");
         }
+
         return new SlideVO(slide);
     }
 
-    private Page<Slide> findByTeacherIdAndKey(Long teacherId, String key, Pageable pageable) {
-        return slideDAO.findAll(
-                (Specification<Slide>) (root, query, cb) -> cb.and(
-                        cb.equal(root.get("teacher").get("id"), teacherId)
-                        , cb.equal(root.get("deleteAt"), 0L)
-                        , cb.like(root.get("name"), StringUtils.keyPattern(key))
-                        , cb.equal(root.get("course").get("deleteAt"), 0L)
-                ),
-                pageable);
+    @Override
+    public boolean existsAddAuth(LoginUser user, Slide slide) {
+        return slide.getCourse().getTeacher().getId().equals(user.getId());
+    }
+
+    @Override
+    public boolean existsDelAuth(LoginUser user, Slide slide) {
+        return slide.getTeacher().getId().equals(user.getId());
     }
 
-    private Page<Slide> findByCourseIdAndKey(Long courseId, String key, Pageable pageable) {
-        return slideDAO.findAll(
-                (Specification<Slide>) (root, query, cb) -> cb.and(
-                        cb.equal(root.get("course").get("id"), courseId)
-                        , cb.equal(root.get("deleteAt"), 0L)
-                        , cb.like(root.get("name"), StringUtils.keyPattern(key))
-                        , cb.equal(root.get("course").get("deleteAt"), 0L)
-                ),
-                pageable);
+    @Override
+    public boolean existsUpdateAuth(LoginUser user, Slide slide) {
+        return slide.getTeacher().getId().equals(user.getId());
     }
 
-    private Page<Slide> findByCourseIdAndKeyAndStateNotIn(Long courseId, Set<SlideState> exclusiveStates, String key, Pageable pageable) {
-        return slideDAO.findAll(
-                (Specification<Slide>) (root, query, cb) -> cb.and(
-                        cb.equal(root.get("course").get("id"), courseId)
-                        , cb.equal(root.get("deleteAt"), 0L)
-                        , cb.like(root.get("name"), StringUtils.keyPattern(key))
-                        , cb.not(root.get("state").in(exclusiveStates))
-                        , cb.equal(root.get("course").get("deleteAt"), 0L)
-                ),
-                pageable);
+    @Override
+    public boolean existsGetAuth(LoginUser user, Slide slide) {
+        return courseService.existsGetAuth(user, courseDAO.findCourseById(slide.getCourse().getId()));
     }
 }

+ 13 - 21
src/main/java/nju/seec/helper/service/impl/UserServiceImpl.java

@@ -1,25 +1,22 @@
 package nju.seec.helper.service.impl;
 
-import com.google.common.collect.ImmutableMap;
+import nju.seec.helper.aspect.auth.LoginUser;
 import nju.seec.helper.dao.UserDAO;
-import nju.seec.helper.dto.user.LoginUser;
 import nju.seec.helper.dto.user.ResetPasswordDTO;
 import nju.seec.helper.dto.user.UserDTO;
 import nju.seec.helper.entity.User;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.enums.UserState;
+import nju.seec.helper.enums.UserType;
+import nju.seec.helper.exception.HelperException;
 import nju.seec.helper.service.UserService;
-import nju.seec.helper.service.util.AuthUtils;
 import nju.seec.helper.util.Consts;
 import nju.seec.helper.util.EncryptUtils;
 import nju.seec.helper.util.RedisCacheUtils;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.enums.UserType;
-import nju.seec.helper.util.exception.HelperException;
 import nju.seec.helper.vo.UserVO;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.Map;
-
 /**
  * @author cst
  */
@@ -27,11 +24,11 @@ import java.util.Map;
 public class UserServiceImpl implements UserService {
     private final RedisCacheUtils cacheUtils;
     private final UserDAO userDAO;
-    private final static Map<String, UserType> EMAIL_SUFFIX_AND_USER_TYPE_MAP =
-            ImmutableMap.of(
-                    "smail.nju.edu.cn", UserType.STUDENT,
-                    "nju.edu.cn", UserType.TEACHER
-            );
+//    private final static Map<String, UserType> EMAIL_SUFFIX_AND_USER_TYPE_MAP =
+//            ImmutableMap.of(
+//                    "smail.nju.edu.cn", UserType.STUDENT,
+//                    "nju.edu.cn", UserType.TEACHER
+//            );
 
     public UserServiceImpl(UserDAO userDAO, RedisCacheUtils cacheUtils) {
         this.userDAO = userDAO;
@@ -54,28 +51,23 @@ public class UserServiceImpl implements UserService {
             throw HelperException.of(ExceptionType.NOT_FOUND, "手机号或验证码错误");
         }
 
-        User user = new User()
+        User user = userDAO.save(new User()
                 .setName(userDTO.getName())
                 .setEmail(userDTO.getEmail())
                 .setPhone(userDTO.getPhone())
                 .setPassword(EncryptUtils.encode(userDTO.getPassword()))
-                .setType(getUserType(email));
+                .setType(userDTO.getType())
+                .setState(userDTO.getType() == UserType.TEACHER ? UserState.CHECKING : UserState.NORMAL));
 
-        user = userDAO.save(user);
         cacheUtils.remove(Consts.EMAIL_CACHE_NAME, email);
         cacheUtils.remove(Consts.PHONE_CACHE_NAME, phone);
         return new UserVO(user);
     }
 
-    private UserType getUserType(String email) {
-        return EMAIL_SUFFIX_AND_USER_TYPE_MAP.get(email.substring(email.indexOf("@") + 1));
-    }
-
     @Transactional(rollbackFor = Exception.class)
     @Override
     public UserVO modifyUser(LoginUser loginUser, UserDTO userDTO) {
         User user = userDAO.findUserById(loginUser.getId());
-        AuthUtils.checkDataAuth(loginUser.getId(), user.getId(), "您无权修改该用户的个人信息");
         user.setName(userDTO.getName());
         user = userDAO.save(user);
         return new UserVO(user);

+ 0 - 18
src/main/java/nju/seec/helper/service/util/AuthUtils.java

@@ -1,18 +0,0 @@
-package nju.seec.helper.service.util;
-
-import lombok.NonNull;
-import lombok.experimental.UtilityClass;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
-
-/**
- * @author cst
- */
-@UtilityClass
-public class AuthUtils {
-    public void checkDataAuth(@NonNull Long userId, @NonNull Long dataUserId, String errMsg) {
-        if (!userId.equals(dataUserId)) {
-            throw HelperException.of(ExceptionType.FORBIDDEN, errMsg);
-        }
-    }
-}

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

@@ -0,0 +1,33 @@
+package nju.seec.helper.service.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;
+    }
+}

+ 4 - 5
src/main/java/nju/seec/helper/util/Consts.java

@@ -4,10 +4,9 @@ package nju.seec.helper.util;
  * @author cst
  */
 public class Consts {
-    public static final String SESSION_USER_NAME = "helper_user";
+    public static final String SYS_NAME = "helper";
 
-    public static final String EMAIL_CACHE_NAME = "email";
-    public static final String PHONE_CACHE_NAME = "phone";
-    public static final String SLIDE_URL_CACHE_NAME = "slide_url";
-    public static final String COURSE_FILE_URL_CACHE_NAME = "course_file_url";
+    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";
 }

+ 9 - 6
src/main/java/nju/seec/helper/util/MailUtils.java

@@ -1,6 +1,7 @@
 package nju.seec.helper.util;
 
-import nju.seec.helper.config.properties.MailProperties;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.mail.SimpleMailMessage;
 import org.springframework.mail.javamail.JavaMailSender;
 import org.springframework.stereotype.Component;
@@ -8,21 +9,23 @@ import org.springframework.stereotype.Component;
 /**
  * @author cst
  */
+@Data
 @Component
+@ConfigurationProperties("helper.mail")
 public class MailUtils {
-    private final MailProperties mailProperties;
+    private String from;
+    private String subject;
     private final JavaMailSender mailSender;
 
-    public MailUtils(MailProperties mailProperties, JavaMailSender mailSender) {
-        this.mailProperties = mailProperties;
+    public MailUtils(JavaMailSender mailSender) {
         this.mailSender = mailSender;
     }
 
     public void sendSimpleMailMessage(String text, String... to) {
         SimpleMailMessage message = new SimpleMailMessage();
-        message.setFrom(mailProperties.getFrom());
+        message.setFrom(from);
         message.setTo(to);
-        message.setSubject(mailProperties.getSubject());
+        message.setSubject(subject);
         message.setText(text);
         mailSender.send(message);
     }

+ 16 - 14
src/main/java/nju/seec/helper/util/FileUtils.java → src/main/java/nju/seec/helper/util/OssUtils.java

@@ -5,10 +5,11 @@ 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.config.properties.OssProperties;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.exception.HelperException;
+import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 
@@ -23,16 +24,17 @@ import java.util.Date;
  */
 @Slf4j
 @Component
-public class FileUtils {
-    private final OssProperties ossProperties;
-
-    public FileUtils(OssProperties ossProperties) {
-        this.ossProperties = ossProperties;
-    }
+@Data
+@ConfigurationProperties("aliyun.oss")
+public class OssUtils {
+    private String endpoint;
+    private String accessKeyId;
+    private String accessKeySecret;
+    private String bucketName;
 
     public void upload(String objectName, InputStream inputStream) {
         OSS ossClient = getOss();
-        PutObjectRequest putObjectRequest = new PutObjectRequest(ossProperties.getBucketName(), objectName, inputStream);
+        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
         try {
             ossClient.putObject(putObjectRequest);
         } catch (OSSException | ClientException e) {
@@ -49,7 +51,7 @@ public class FileUtils {
         OSS ossClient = getOss();
         Date expiration = new Date(System.currentTimeMillis() + seconds * 1000);
         try {
-            URL url = ossClient.generatePresignedUrl(ossProperties.getBucketName(), objectName, expiration);
+            URL url = ossClient.generatePresignedUrl(bucketName, objectName, expiration);
             return url.toString();
         } catch (OSSException | ClientException e) {
             log.error(e.getMessage());
@@ -64,7 +66,7 @@ public class FileUtils {
     public void copy(String sourceObjectName, String destinationObjectName) {
         OSS ossClient = getOss();
         try {
-            ossClient.copyObject(ossProperties.getBucketName(), sourceObjectName, ossProperties.getBucketName(), destinationObjectName);
+            ossClient.copyObject(bucketName, sourceObjectName, bucketName, destinationObjectName);
         } catch (OSSException | ClientException e) {
             log.error(e.getMessage());
             throw HelperException.of(ExceptionType.ERROR, "文件复制失败");
@@ -79,7 +81,7 @@ public class FileUtils {
     public void delete(String objectName) {
         OSS ossClient = getOss();
         try {
-            ossClient.deleteObject(ossProperties.getBucketName(), objectName);
+            ossClient.deleteObject(bucketName, objectName);
         } catch (OSSException | ClientException e) {
             log.error(e.getMessage());
             throw HelperException.of(ExceptionType.ERROR, "文件删除失败");
@@ -91,6 +93,6 @@ public class FileUtils {
     }
 
     private OSS getOss() {
-        return new OSSClientBuilder().build(ossProperties.getEndpoint(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret());
+        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
     }
 }

+ 7 - 8
src/main/java/nju/seec/helper/util/RedisCacheUtils.java

@@ -2,7 +2,6 @@ package nju.seec.helper.util;
 
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
 
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -22,23 +21,23 @@ public class RedisCacheUtils {
     }
 
     public void set(String cacheName, String key, String value, long expireTime, TimeUnit timeUnit) {
-        redisTemplate.opsForValue().set(cacheName + ":" + key, value, expireTime, timeUnit);
-    }
-
-    public void setAll(String cacheName, String key, String value, long expireTime, TimeUnit timeUnit) {
-        throw new NotImplementedException();
+        redisTemplate.opsForValue().set(combineKey(cacheName, key), value, expireTime, timeUnit);
     }
 
     public String get(String cacheName, String key) {
-        return redisTemplate.opsForValue().get(cacheName + ":" + key);
+        return redisTemplate.opsForValue().get(combineKey(cacheName, key));
     }
 
     public List<String> multiGet(String cacheName, List<String> keys) {
-        List<String> body = keys.stream().map(s -> cacheName + ":" + s).collect(Collectors.toList());
+        List<String> body = keys.stream().map(key -> combineKey(cacheName, key)).collect(Collectors.toList());
         return redisTemplate.opsForValue().multiGet(body);
     }
 
     public void remove(String cacheName, String key) {
         redisTemplate.delete(cacheName + ":" + key);
     }
+
+    private String combineKey(String cacheName, String key) {
+        return cacheName + ":" + key;
+    }
 }

+ 12 - 10
src/main/java/nju/seec/helper/util/SmsUtils.java

@@ -9,9 +9,9 @@ import com.aliyuncs.http.MethodType;
 import com.aliyuncs.profile.DefaultProfile;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
-import nju.seec.helper.config.properties.SmsProperties;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
+import nju.seec.helper.enums.ExceptionType;
+import nju.seec.helper.exception.HelperException;
+import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
 /**
@@ -19,16 +19,18 @@ import org.springframework.stereotype.Component;
  */
 @Component
 @Slf4j
+@Data
+@ConfigurationProperties("aliyun.sms")
 public class SmsUtils {
     private static final String OK = "OK";
-    private final SmsProperties smsProperties;
 
-    public SmsUtils(SmsProperties smsProperties) {
-        this.smsProperties = smsProperties;
-    }
+    private String accessKeyId;
+    private String accessSecret;
+    private String signName;
+    private String templateCode;
 
     public void sendSmsCode(String to, String code) {
-        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", smsProperties.getAccessKeyId(), smsProperties.getAccessSecret());
+        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessSecret);
         IAcsClient client = new DefaultAcsClient(profile);
 
         CommonRequest request = new CommonRequest();
@@ -38,8 +40,8 @@ public class SmsUtils {
         request.setAction("SendSms");
         request.putQueryParameter("RegionId", "cn-hangzhou");
         request.putQueryParameter("PhoneNumbers", to);
-        request.putQueryParameter("SignName", smsProperties.getSignName());
-        request.putQueryParameter("TemplateCode", smsProperties.getTemplateCode());
+        request.putQueryParameter("SignName", signName);
+        request.putQueryParameter("TemplateCode", templateCode);
         request.putQueryParameter("TemplateParam", "{\"code\":" + code + "}");
         try {
             CommonResponse response = client.getCommonResponse(request);

+ 1 - 1
src/main/java/nju/seec/helper/service/util/StringUtils.java → src/main/java/nju/seec/helper/util/StringUtils.java

@@ -1,4 +1,4 @@
-package nju.seec.helper.service.util;
+package nju.seec.helper.util;
 
 import lombok.experimental.UtilityClass;
 

+ 0 - 53
src/main/java/nju/seec/helper/util/enums/QuizState.java

@@ -1,53 +0,0 @@
-package nju.seec.helper.util.enums;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import lombok.NonNull;
-import lombok.val;
-
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @author xst
- * <p>
- * updated by cst
- */
-public enum QuizState {
-    NOT_STARTED("未开始"), ONGOING("正在进行"), CLOSED("已结束");
-    private String name;
-
-    QuizState(String name) {
-        this.name = name;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    private static final Map<SlideState, Map<QuizState, Set<QuizType>>> MAP = ImmutableMap.of(
-            SlideState.DRAFT, ImmutableMap.of(NOT_STARTED, ImmutableSet.of(QuizType.BEFORE_CLASS, QuizType.IN_CLASS, QuizType.AFTER_CLASS)),
-            SlideState.BEFORE_CLASS, ImmutableMap.of(NOT_STARTED, ImmutableSet.of(QuizType.IN_CLASS, QuizType.AFTER_CLASS),
-                    ONGOING, ImmutableSet.of(QuizType.BEFORE_CLASS)),
-            SlideState.IN_CLASS, ImmutableMap.of(NOT_STARTED, ImmutableSet.of(QuizType.AFTER_CLASS),
-                    ONGOING, ImmutableSet.of(QuizType.IN_CLASS),
-                    CLOSED, ImmutableSet.of(QuizType.BEFORE_CLASS)),
-            SlideState.AFTER_CLASS, ImmutableMap.of(ONGOING, ImmutableSet.of(QuizType.AFTER_CLASS),
-                    CLOSED, ImmutableSet.of(QuizType.BEFORE_CLASS, QuizType.IN_CLASS)),
-            SlideState.FINISH, ImmutableMap.of(CLOSED, ImmutableSet.of(QuizType.BEFORE_CLASS, QuizType.IN_CLASS, QuizType.AFTER_CLASS))
-    );
-
-    public static QuizState getQuizStateBySlideStateAndQuizType(@NonNull SlideState slideState, @NonNull QuizType quizType) {
-        Map<QuizState, Set<QuizType>> map = MAP.get(slideState);
-        for (val entry : map.entrySet()) {
-            if (entry.getValue().contains(quizType)) {
-                return entry.getKey();
-            }
-        }
-        return null;
-    }
-}

+ 0 - 39
src/main/java/nju/seec/helper/util/enums/QuizType.java

@@ -1,39 +0,0 @@
-package nju.seec.helper.util.enums;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import lombok.NonNull;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @author cst
- */
-public enum QuizType {
-    /**
-     * 课前测试
-     */
-    BEFORE_CLASS,
-    /**
-     * 课中测试
-     */
-    IN_CLASS,
-    /**
-     * 课后测试
-     */
-    AFTER_CLASS;
-
-    private final static Map<SlideState, Set<QuizType>> SLIDE_QUIZ_MAP = ImmutableMap.of(
-            SlideState.DRAFT, ImmutableSet.of(BEFORE_CLASS, IN_CLASS, AFTER_CLASS),
-            SlideState.BEFORE_CLASS, ImmutableSet.of(BEFORE_CLASS, IN_CLASS, AFTER_CLASS),
-            SlideState.IN_CLASS, ImmutableSet.of(IN_CLASS, AFTER_CLASS),
-            SlideState.AFTER_CLASS, ImmutableSet.of(AFTER_CLASS),
-            SlideState.FINISH, Collections.emptySet()
-    );
-
-    public static Set<QuizType> getQuizTypesBySlideState(@NonNull SlideState slideState) {
-        return SLIDE_QUIZ_MAP.get(slideState);
-    }
-}

+ 0 - 9
src/main/java/nju/seec/helper/util/enums/UserType.java

@@ -1,9 +0,0 @@
-package nju.seec.helper.util.enums;
-
-/**
- * @author cst
- */
-
-public enum UserType {
-    TEACHER, STUDENT
-}

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

@@ -0,0 +1,14 @@
+package nju.seec.helper.util.file;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author cst
+ */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class FileInfo {
+    private int pages;
+    private String extension;
+}

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.