فهرست منبع

Merge branch 'iterator1' into quiz

# Conflicts:
#	src/main/java/nju/seec/helper/HelperApplication.java
#	src/main/java/nju/seec/helper/controller/ControllerAdvice.java
#	src/main/java/nju/seec/helper/controller/response/PageResponse.java
#	src/main/java/nju/seec/helper/dao/ChooseDAO.java
#	src/main/java/nju/seec/helper/service/impl/SlideServiceImpl.java
#	src/main/java/nju/seec/helper/util/JsonUtils.java
XuShengTao 6 سال پیش
والد
کامیت
19876f8fa1
88فایلهای تغییر یافته به همراه1856 افزوده شده و 567 حذف شده
  1. 2 0
      .gitignore
  2. 4 0
      pom.xml
  3. 2 0
      src/main/java/nju/seec/helper/HelperApplication.java
  4. 0 72
      src/main/java/nju/seec/helper/InitDataComponent.java
  5. 0 16
      src/main/java/nju/seec/helper/ServletInitializer.java
  6. 0 39
      src/main/java/nju/seec/helper/config/Swagger2Config.java
  7. 16 0
      src/main/java/nju/seec/helper/config/WebsocketConfig.java
  8. 8 13
      src/main/java/nju/seec/helper/controller/CodeController.java
  9. 21 9
      src/main/java/nju/seec/helper/controller/CommentController.java
  10. 65 0
      src/main/java/nju/seec/helper/controller/CommentWebsocket.java
  11. 18 6
      src/main/java/nju/seec/helper/controller/ControllerAdvice.java
  12. 30 11
      src/main/java/nju/seec/helper/controller/CourseController.java
  13. 56 0
      src/main/java/nju/seec/helper/controller/MessageController.java
  14. 58 0
      src/main/java/nju/seec/helper/controller/NoticeController.java
  15. 3 4
      src/main/java/nju/seec/helper/controller/ReplyController.java
  16. 48 28
      src/main/java/nju/seec/helper/controller/SlideController.java
  17. 13 8
      src/main/java/nju/seec/helper/controller/UserController.java
  18. 2 4
      src/main/java/nju/seec/helper/controller/response/PageResponse.java
  19. 12 6
      src/main/java/nju/seec/helper/dao/ChooseDAO.java
  20. 21 5
      src/main/java/nju/seec/helper/dao/CommentDAO.java
  21. 25 9
      src/main/java/nju/seec/helper/dao/CourseDAO.java
  22. 56 0
      src/main/java/nju/seec/helper/dao/MessageDAO.java
  23. 40 0
      src/main/java/nju/seec/helper/dao/NoticeDAO.java
  24. 2 2
      src/main/java/nju/seec/helper/dao/ReplyDAO.java
  25. 54 9
      src/main/java/nju/seec/helper/dao/SlideDAO.java
  26. 2 2
      src/main/java/nju/seec/helper/dao/UserDAO.java
  27. 6 4
      src/main/java/nju/seec/helper/dto/ChooseDTO.java
  28. 11 6
      src/main/java/nju/seec/helper/dto/CommentDTO.java
  29. 11 2
      src/main/java/nju/seec/helper/dto/CourseDTO.java
  30. 7 6
      src/main/java/nju/seec/helper/dto/LoginDTO.java
  31. 1 1
      src/main/java/nju/seec/helper/dto/LoginUser.java
  32. 17 0
      src/main/java/nju/seec/helper/dto/MessageDTO.java
  33. 0 23
      src/main/java/nju/seec/helper/dto/ModifyCourseDTO.java
  34. 0 17
      src/main/java/nju/seec/helper/dto/ModifySlideDTO.java
  35. 0 16
      src/main/java/nju/seec/helper/dto/ModifyUserDTO.java
  36. 27 0
      src/main/java/nju/seec/helper/dto/NoticeDTO.java
  37. 0 33
      src/main/java/nju/seec/helper/dto/RegisterDTO.java
  38. 8 4
      src/main/java/nju/seec/helper/dto/ReplyDTO.java
  39. 10 7
      src/main/java/nju/seec/helper/dto/ResetPasswordDTO.java
  40. 16 5
      src/main/java/nju/seec/helper/dto/SlideDTO.java
  41. 19 0
      src/main/java/nju/seec/helper/dto/SlideFileDTO.java
  42. 6 4
      src/main/java/nju/seec/helper/dto/SlideStateDTO.java
  43. 34 0
      src/main/java/nju/seec/helper/dto/UserDTO.java
  44. 9 0
      src/main/java/nju/seec/helper/dto/groups/Create.java
  45. 9 0
      src/main/java/nju/seec/helper/dto/groups/Modify.java
  46. 20 0
      src/main/java/nju/seec/helper/dto/validator/phone/Phone.java
  47. 21 0
      src/main/java/nju/seec/helper/dto/validator/phone/PhoneValidator.java
  48. 32 0
      src/main/java/nju/seec/helper/dto/validator/schoolemail/SchoolEmail.java
  49. 54 0
      src/main/java/nju/seec/helper/dto/validator/schoolemail/SchoolEmailValidator.java
  50. 5 4
      src/main/java/nju/seec/helper/entity/Choose.java
  51. 11 4
      src/main/java/nju/seec/helper/entity/Comment.java
  52. 4 2
      src/main/java/nju/seec/helper/entity/Course.java
  53. 47 0
      src/main/java/nju/seec/helper/entity/Message.java
  54. 43 0
      src/main/java/nju/seec/helper/entity/Notice.java
  55. 7 3
      src/main/java/nju/seec/helper/entity/Reply.java
  56. 9 4
      src/main/java/nju/seec/helper/entity/Slide.java
  57. 6 2
      src/main/java/nju/seec/helper/entity/User.java
  58. 13 5
      src/main/java/nju/seec/helper/service/CommentService.java
  59. 21 5
      src/main/java/nju/seec/helper/service/CourseService.java
  60. 61 0
      src/main/java/nju/seec/helper/service/MessageService.java
  61. 49 0
      src/main/java/nju/seec/helper/service/NoticeService.java
  62. 1 1
      src/main/java/nju/seec/helper/service/ReplyService.java
  63. 32 15
      src/main/java/nju/seec/helper/service/SlideService.java
  64. 6 7
      src/main/java/nju/seec/helper/service/UserService.java
  65. 31 10
      src/main/java/nju/seec/helper/service/impl/CommentServiceImpl.java
  66. 38 19
      src/main/java/nju/seec/helper/service/impl/CourseServiceImpl.java
  67. 65 0
      src/main/java/nju/seec/helper/service/impl/MessageServiceImpl.java
  68. 74 0
      src/main/java/nju/seec/helper/service/impl/NoticeServiceImpl.java
  69. 21 6
      src/main/java/nju/seec/helper/service/impl/ReplyServiceImpl.java
  70. 119 42
      src/main/java/nju/seec/helper/service/impl/SlideServiceImpl.java
  71. 27 24
      src/main/java/nju/seec/helper/service/impl/UserServiceImpl.java
  72. 4 4
      src/main/java/nju/seec/helper/service/util/AuthUtils.java
  73. 34 0
      src/main/java/nju/seec/helper/service/util/RefreshUtils.java
  74. 0 3
      src/main/java/nju/seec/helper/util/Consts.java
  75. 64 9
      src/main/java/nju/seec/helper/util/FileUtils.java
  76. 9 9
      src/main/java/nju/seec/helper/util/JsonUtils.java
  77. 23 0
      src/main/java/nju/seec/helper/util/enums/MessageType.java
  78. 21 0
      src/main/java/nju/seec/helper/util/serializer/LocalDateTimeSerializer.java
  79. 4 4
      src/main/java/nju/seec/helper/vo/CommentVO.java
  80. 2 2
      src/main/java/nju/seec/helper/vo/CourseVO.java
  81. 28 0
      src/main/java/nju/seec/helper/vo/NoticeVO.java
  82. 1 1
      src/main/java/nju/seec/helper/vo/ReplyVO.java
  83. 3 3
      src/main/java/nju/seec/helper/vo/SlideVO.java
  84. 1 1
      src/main/java/nju/seec/helper/vo/UserVO.java
  85. 58 0
      src/main/resources/application-deploy.yml
  86. 10 6
      src/main/resources/application-dev.yml
  87. 27 0
      src/main/resources/update.sql
  88. 1 1
      src/test/java/nju/seec/helper/HelperApplicationTests.java

+ 2 - 0
.gitignore

@@ -32,3 +32,5 @@ build/
 
 ### NPM ###
 node_modules/
+
+.DS_Store

+ 4 - 0
pom.xml

@@ -57,6 +57,10 @@
             <artifactId>spring-boot-starter-tomcat</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>

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

@@ -3,6 +3,7 @@ package nju.seec.helper;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
+import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 
@@ -11,6 +12,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
  */
 @ConfigurationPropertiesScan("nju.seec.helper.config.properties")
 @EnableTransactionManagement
+@EnableAsync
 @EnableJpaAuditing
 @SpringBootApplication
 public class HelperApplication {

+ 0 - 72
src/main/java/nju/seec/helper/InitDataComponent.java

@@ -1,72 +0,0 @@
-//package nju.seec.helper;
-//
-//import com.github.javafaker.Faker;
-//import com.google.common.collect.ImmutableList;
-//import nju.seec.helper.dao.CourseDAO;
-//import nju.seec.helper.dao.UserDAO;
-//import nju.seec.helper.entity.Course;
-//import nju.seec.helper.entity.User;
-//import nju.seec.helper.util.EncryptUtils;
-//import nju.seec.helper.util.enums.UserType;
-//import org.springframework.boot.ApplicationArguments;
-//import org.springframework.boot.ApplicationRunner;
-//import org.springframework.stereotype.Component;
-//
-//import java.util.List;
-//
-///**
-// * @author cst
-// */
-//@Component
-//public class InitDataComponent implements ApplicationRunner {
-//    private final UserDAO userDAO;
-//    private final CourseDAO courseDAO;
-//
-//    public InitDataComponent(UserDAO userDAO, CourseDAO courseDAO) {
-//        this.userDAO = userDAO;
-//        this.courseDAO = courseDAO;
-//    }
-//
-//    @Override
-//    public void run(ApplicationArguments args) {
-//        Faker faker = new Faker();
-//        // init user
-//        String teacherName = faker.name().name();
-//        List<User> users = ImmutableList.of(
-//                new User().setName(teacherName)
-//                        .setEmail(faker.internet().emailAddress())
-//                        .setPhone(faker.phoneNumber().phoneNumber())
-//                        .setPassword(EncryptUtils.encode("teacher"))
-//                        .setType(UserType.TEACHER),
-//                new User().setName(faker.name().name())
-//                        .setEmail(faker.internet().emailAddress())
-//                        .setPhone(faker.phoneNumber().phoneNumber())
-//                        .setPassword(EncryptUtils.encode("student"))
-//                        .setType(UserType.STUDENT)
-//        );
-//        userDAO.saveAll(users);
-//
-//        // init course
-//        List<Course> courses = ImmutableList.of(
-//                new Course().setTeacherId(1)
-//                        .setTeacherName(teacherName)
-//                        .setName(faker.educator().course())
-//                        .setBio("")
-//                        .setCode(EncryptUtils.encode("123456"))
-//                        .setCover(""),
-//                new Course().setTeacherId(1)
-//                        .setTeacherName(teacherName)
-//                        .setName(faker.educator().course())
-//                        .setBio("")
-//                        .setCode(EncryptUtils.encode("123456"))
-//                        .setCover(""),
-//                new Course().setTeacherId(1)
-//                        .setTeacherName(teacherName)
-//                        .setName(faker.educator().course())
-//                        .setBio("")
-//                        .setCode(EncryptUtils.encode("123456"))
-//                        .setCover("")
-//        );
-//        courseDAO.saveAll(courses);
-//    }
-//}

+ 0 - 16
src/main/java/nju/seec/helper/ServletInitializer.java

@@ -1,16 +0,0 @@
-//package nju.seec.helper;
-//
-//import org.springframework.boot.builder.SpringApplicationBuilder;
-//import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
-//
-///**
-// * @author cst
-// */
-//public class ServletInitializer extends SpringBootServletInitializer {
-//
-//    @Override
-//    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
-//        return application.sources(HelperApplication.class);
-//    }
-//
-//}

+ 0 - 39
src/main/java/nju/seec/helper/config/Swagger2Config.java

@@ -1,39 +0,0 @@
-//package nju.seec.helper.config;
-//
-//import io.swagger.annotations.Api;
-//import org.springframework.context.annotation.Bean;
-//import org.springframework.context.annotation.Configuration;
-//import springfox.documentation.builders.ApiInfoBuilder;
-//import springfox.documentation.builders.PathSelectors;
-//import springfox.documentation.builders.RequestHandlerSelectors;
-//import springfox.documentation.service.ApiInfo;
-//import springfox.documentation.service.Contact;
-//import springfox.documentation.spi.DocumentationType;
-//import springfox.documentation.spring.web.plugins.Docket;
-//import springfox.documentation.swagger2.annotations.EnableSwagger2;
-//
-///**
-// * @author cst
-// */
-//@Configuration
-//@EnableSwagger2
-//public class Swagger2Config {
-//    @Bean
-//    public Docket createDocket() {
-//        return new Docket(DocumentationType.SWAGGER_2)
-//                .apiInfo(apiInfo())
-//                .select()
-//                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
-//                .paths(PathSelectors.any())
-//                .build();
-//    }
-//
-//    private ApiInfo apiInfo() {
-//        return new ApiInfoBuilder()
-//                .title("SEEC-Helper后端API文档")
-//                .contact(new Contact("ChenSiTong", "", "chen_st@foxmail.com"))
-//                .description("")
-//                .version("1.0")
-//                .build();
-//    }
-//}

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

@@ -0,0 +1,16 @@
+package nju.seec.helper.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+/**
+ * @author cst
+ */
+@Configuration
+public class WebsocketConfig {
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+}

+ 8 - 13
src/main/java/nju/seec/helper/controller/CodeController.java

@@ -1,20 +1,21 @@
 package nju.seec.helper.controller;
 
+import nju.seec.helper.dto.validator.phone.Phone;
+import nju.seec.helper.dto.validator.schoolemail.SchoolEmail;
 import nju.seec.helper.service.CodeService;
-import nju.seec.helper.util.Consts;
-import nju.seec.helper.util.enums.ExceptionType;
-import nju.seec.helper.util.exception.HelperException;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
-import java.util.regex.Pattern;
+import javax.validation.constraints.NotBlank;
 
 /**
  * 发送验证码
  *
  * @author cst
  */
+@Validated
 @RestController
 @RequestMapping("/api/code")
 public class CodeController {
@@ -31,24 +32,18 @@ public class CodeController {
      * @return
      */
     @PostMapping("/email")
-    public void sendEmailCode(String email) {
-        if (email == null || !Pattern.matches(Consts.SCHOOL_EMAIL_REGEX, email)) {
-            throw HelperException.of(ExceptionType.PARAM_ERROR, "该邮箱不是南大校邮");
-        }
+    public void sendEmailCode(@NotBlank(message = "邮箱不能为空") @SchoolEmail String email) {
         codeService.sendEmailCode(email);
     }
 
     /**
-     * 发送手机验证码(TODO)
+     * 发送手机验证码
      *
      * @param phone
      * @return
      */
     @PostMapping("/phone")
-    public void sendPhoneCode(String phone) {
-        if (phone == null || !Pattern.matches(Consts.PHONE_REGEX, phone)) {
-            throw HelperException.of(ExceptionType.PARAM_ERROR, "手机号格式错误");
-        }
+    public void sendPhoneCode(@NotBlank(message = "手机号不能为空") @Phone String phone) {
         codeService.sendPhoneCode(phone);
     }
 }

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

@@ -5,14 +5,14 @@ import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.CommentDTO;
 import nju.seec.helper.dto.LoginUser;
 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;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import javax.validation.Valid;
-
 /**
  * 评论
  *
@@ -33,8 +33,10 @@ public class CommentController {
     @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "发表评论")
     @PostMapping
     public CommentVO createComment(LoginUser user,
-                                   @Valid @RequestBody CommentDTO commentDTO) {
-        return commentService.createComment(user, commentDTO);
+                                   @Validated @RequestBody CommentDTO commentDTO) {
+        CommentVO commentVO = commentService.createComment(user, commentDTO);
+        CommentWebsocket.sendAllMessage(commentDTO.getSlideId(), JsonUtils.toJson(commentVO));
+        return commentVO;
     }
 
     /**
@@ -43,7 +45,7 @@ public class CommentController {
     @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "删除评论")
     @DeleteMapping("/{commentId}")
     public void removeComment(LoginUser user,
-                              @PathVariable Integer commentId) {
+                              @PathVariable Long commentId) {
         commentService.removeComment(user, commentId);
     }
 
@@ -53,7 +55,7 @@ public class CommentController {
     @Auth(roles = UserType.TEACHER, message = "置顶评论")
     @PostMapping("/{commentId}/top")
     public void topComment(LoginUser user,
-                           @PathVariable Integer commentId) {
+                           @PathVariable Long commentId) {
         commentService.topComment(user, commentId);
     }
 
@@ -63,17 +65,27 @@ public class CommentController {
     @Auth(roles = UserType.TEACHER, message = "取消置顶评论")
     @PostMapping("/{commentId}/unTop")
     public void unTopComment(LoginUser user,
-                             @PathVariable Integer commentId) {
+                             @PathVariable Long commentId) {
         commentService.unTopComment(user, commentId);
     }
 
     /**
      * 取得某一页的评论信息
      */
+    @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取评论")
     @GetMapping("/slide/{slideId}")
-    public PageResponse<CommentVO> getComments(@PathVariable Integer slideId,
+    public PageResponse<CommentVO> getComments(@PathVariable Long slideId,
                                                @RequestParam(required = false, defaultValue = "1") Integer pageNumber,
                                                @PageableDefault(Integer.MAX_VALUE) Pageable pageable) {
-        return PageResponse.of(commentService.getCommentsBySlideIdAndPageNumber(slideId, pageNumber, pageable));
+        return PageResponse.of(commentService.getCommentsBySlideAndPageNumber(slideId, pageNumber, pageable));
+    }
+
+    /**
+     * 取得某评论
+     */
+    @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取评论")
+    @GetMapping("/{commentId}")
+    public CommentVO getOneComment(@PathVariable Long commentId) {
+        return commentService.getOneComment(commentId);
     }
 }

+ 65 - 0
src/main/java/nju/seec/helper/controller/CommentWebsocket.java

@@ -0,0 +1,65 @@
+package nju.seec.helper.controller;
+
+import lombok.EqualsAndHashCode;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * @author cst
+ */
+@ServerEndpoint("/api/ws/comment/slide/{slideId}")
+@Slf4j
+@EqualsAndHashCode
+@Component
+public class CommentWebsocket {
+    private static Map<Long, Set<CommentWebsocket>> COMMENT_WEBSOCKET_MAP = new ConcurrentHashMap<>();
+    private Long slideId;
+    private Session session;
+
+    @OnOpen
+    public void onOpen(Session session, @PathParam("slideId") Long slideId) {
+        // 3小时有效期
+        session.setMaxIdleTimeout(3 * 60 * 60 * 1000);
+        this.session = session;
+        this.slideId = slideId;
+        if (!COMMENT_WEBSOCKET_MAP.containsKey(slideId)) {
+            COMMENT_WEBSOCKET_MAP.put(slideId, new CopyOnWriteArraySet<>());
+        }
+        COMMENT_WEBSOCKET_MAP.get(slideId).add(this);
+    }
+
+    @OnClose
+    public void onClose() {
+        COMMENT_WEBSOCKET_MAP.get(slideId).remove(this);
+    }
+
+    @OnError
+    public void onError(Throwable throwable) {
+        log.error(throwable.getLocalizedMessage(), throwable);
+    }
+
+    public static void sendAllMessage(Long slideId, String message) {
+        COMMENT_WEBSOCKET_MAP
+                .getOrDefault(slideId, Collections.emptySet())
+                .parallelStream()
+                .forEach(commentWebsocket -> {
+                    try {
+                        commentWebsocket.session.getAsyncRemote().sendText(message);
+                    } catch (Exception e) {
+                        log.error(e.getLocalizedMessage());
+                    }
+                });
+    }
+}

+ 18 - 6
src/main/java/nju/seec/helper/controller/ControllerAdvice.java

@@ -5,10 +5,14 @@ import nju.seec.helper.controller.response.ErrorResponse;
 import nju.seec.helper.util.exception.HelperException;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
+import org.springframework.validation.BindException;
 import org.springframework.web.bind.MethodArgumentNotValidException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
 
+import javax.validation.ConstraintViolationException;
+import java.util.Objects;
+
 /**
  * @author cst
  */
@@ -16,8 +20,18 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
 @RestControllerAdvice
 public class ControllerAdvice {
     @ExceptionHandler(MethodArgumentNotValidException.class)
-    public ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
-        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ErrorResponse.of("请检查您输入的数据"));
+    public ResponseEntity<ErrorResponse> handleMethodArgumentNotValidatedException(MethodArgumentNotValidException e) {
+        return ResponseEntity.badRequest().body(ErrorResponse.of(Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage()));
+    }
+
+    @ExceptionHandler(ConstraintViolationException.class)
+    public ResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationException e) {
+        return ResponseEntity.badRequest().body(ErrorResponse.of(e.getLocalizedMessage()));
+    }
+
+    @ExceptionHandler(BindException.class)
+    public ResponseEntity<ErrorResponse> handleBindException(BindException e) {
+        return ResponseEntity.badRequest().body(ErrorResponse.of(Objects.requireNonNull(e.getFieldError()).getDefaultMessage()));
     }
 
     @ExceptionHandler(HelperException.class)
@@ -27,9 +41,7 @@ public class ControllerAdvice {
 
     @ExceptionHandler(Exception.class)
     public ResponseEntity<ErrorResponse> handleException(Exception e) {
-        //todo: 方便调试
-        log.error(e.getLocalizedMessage());
-        e.printStackTrace();
-        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ErrorResponse.of("系统异常,请重试:"+e.getLocalizedMessage()));
+        log.error(e.getLocalizedMessage(), e);
+        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ErrorResponse.of("系统异常,请重试"));
     }
 }

+ 30 - 11
src/main/java/nju/seec/helper/controller/CourseController.java

@@ -5,16 +5,16 @@ import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.ChooseDTO;
 import nju.seec.helper.dto.CourseDTO;
 import nju.seec.helper.dto.LoginUser;
-import nju.seec.helper.dto.ModifyCourseDTO;
+import nju.seec.helper.dto.groups.Create;
+import nju.seec.helper.dto.groups.Modify;
 import nju.seec.helper.service.CourseService;
 import nju.seec.helper.util.enums.UserType;
 import nju.seec.helper.vo.CourseVO;
 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.validation.Valid;
-
 /**
  * 课程
  *
@@ -35,7 +35,7 @@ public class CourseController {
     @Auth(roles = UserType.TEACHER, message = "创建课程")
     @PostMapping
     public CourseVO createCourse(LoginUser user,
-                                 @Valid @RequestBody CourseDTO courseDTO) {
+                                 @Validated(Create.class) @RequestBody CourseDTO courseDTO) {
         return courseService.createCourse(user, courseDTO);
     }
 
@@ -45,7 +45,7 @@ public class CourseController {
     @Auth(roles = UserType.TEACHER, message = "删除课程")
     @DeleteMapping("/{courseId}")
     public void removeCourse(LoginUser user,
-                             @PathVariable Integer courseId) {
+                             @PathVariable Long courseId) {
         courseService.removeCourse(user, courseId);
     }
 
@@ -55,8 +55,8 @@ public class CourseController {
     @Auth(roles = UserType.TEACHER, message = "修改课程")
     @PutMapping
     public CourseVO modifyCourse(LoginUser user,
-                                 @Valid @RequestBody ModifyCourseDTO modifyCourseDTO) {
-        return courseService.modifyCourse(user, modifyCourseDTO);
+                                 @Validated(Modify.class) @RequestBody CourseDTO courseDTO) {
+        return courseService.modifyCourse(user, courseDTO);
     }
 
     /**
@@ -65,17 +65,17 @@ public class CourseController {
     @Auth(roles = UserType.STUDENT, message = "选课")
     @PostMapping("/choose")
     public void chooseCourse(LoginUser user,
-                             @Valid @RequestBody ChooseDTO chooseDTO) {
+                             @Validated @RequestBody ChooseDTO chooseDTO) {
         courseService.chooseCourse(user, chooseDTO);
     }
 
     /**
-     *
+     * 学生退课
      */
-    @Auth(roles = UserType.STUDENT, message = "取消选课")
+    @Auth(roles = UserType.STUDENT, message = "退课")
     @PostMapping("/quit/{courseId}")
     public void quitCourse(LoginUser user,
-                           @PathVariable Integer courseId) {
+                           @PathVariable Long courseId) {
         courseService.quitCourse(user, courseId);
     }
 
@@ -109,4 +109,23 @@ public class CourseController {
                                                     @PageableDefault(Integer.MAX_VALUE) Pageable pageable) {
         return PageResponse.of(courseService.studentGetCourses(user, key, pageable));
     }
+
+    /**
+     * 获取课程选课码
+     */
+    @Auth(roles = UserType.TEACHER, message = "获取选课码")
+    @GetMapping("/{courseId}/code")
+    public String getCourseCode(LoginUser user,
+                                @PathVariable Long courseId) {
+        return courseService.getCourseCode(user, courseId);
+    }
+
+    /**
+     * 获取某课程
+     */
+    @Auth(roles = {UserType.STUDENT, UserType.TEACHER}, message = "获取课程")
+    @GetMapping("/{courseId}")
+    public CourseVO getOneCourse(@PathVariable Long courseId) {
+        return courseService.getOneCourse(courseId);
+    }
 }

+ 56 - 0
src/main/java/nju/seec/helper/controller/MessageController.java

@@ -0,0 +1,56 @@
+package nju.seec.helper.controller;
+
+import nju.seec.helper.aspect.auth.Auth;
+import nju.seec.helper.controller.response.PageResponse;
+import nju.seec.helper.dto.LoginUser;
+import nju.seec.helper.dto.MessageDTO;
+import nju.seec.helper.entity.Message;
+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
+@RequestMapping("/api/message")
+public class MessageController {
+    private final MessageService messageService;
+
+    public MessageController(MessageService messageService) {
+        this.messageService = messageService;
+    }
+
+    @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "设置已读")
+    @PutMapping("/read")
+    public void readMessages(LoginUser user,
+                             @Validated @RequestBody MessageDTO messageDTO) {
+        messageService.readMessages(user, messageDTO);
+    }
+
+    @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "删除消息")
+    @PutMapping("/delete")
+    public void deleteMessages(LoginUser user,
+                               @Validated @RequestBody MessageDTO messageDTO) {
+        messageService.deleteMessages(user, messageDTO);
+    }
+
+    @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取未读消息")
+    @GetMapping("/unread")
+    public PageResponse<Message> getUnreadMessages(LoginUser user,
+                                                   @RequestParam(required = false, defaultValue = "") String key,
+                                                   @PageableDefault(Integer.MAX_VALUE) Pageable pageable) {
+        return PageResponse.of(messageService.getUnreadMessages(user, key, pageable));
+    }
+
+    @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取已读消息")
+    @GetMapping("/read")
+    public PageResponse<Message> getReadMessages(LoginUser user,
+                                                 @RequestParam(required = false, defaultValue = "") String key,
+                                                 @PageableDefault(Integer.MAX_VALUE) Pageable pageable) {
+        return PageResponse.of(messageService.getReadMessages(user, key, pageable));
+    }
+}

+ 58 - 0
src/main/java/nju/seec/helper/controller/NoticeController.java

@@ -0,0 +1,58 @@
+package nju.seec.helper.controller;
+
+import nju.seec.helper.aspect.auth.Auth;
+import nju.seec.helper.controller.response.PageResponse;
+import nju.seec.helper.dto.LoginUser;
+import nju.seec.helper.dto.NoticeDTO;
+import nju.seec.helper.dto.groups.Create;
+import nju.seec.helper.dto.groups.Modify;
+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;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author cst
+ */
+@RestController
+@RequestMapping("/api/notice")
+public class NoticeController {
+    private final NoticeService noticeService;
+
+    public NoticeController(NoticeService noticeService) {
+        this.noticeService = noticeService;
+    }
+
+    @Auth(roles = UserType.TEACHER, message = "发布公告")
+    @PostMapping
+    public NoticeVO createNotice(LoginUser user,
+                                 @Validated(Create.class) @RequestBody NoticeDTO noticeDTO) {
+        return noticeService.create(user, noticeDTO);
+    }
+
+    @Auth(roles = UserType.TEACHER, message = "修改公告")
+    @PutMapping
+    public NoticeVO modifyNotice(LoginUser user,
+                                 @Validated(Modify.class) @RequestBody NoticeDTO noticeDTO) {
+        return noticeService.modify(user, noticeDTO);
+    }
+
+    @Auth(roles = UserType.TEACHER, message = "删除公告")
+    @DeleteMapping("/{noticeId}")
+    public void removeNotice(LoginUser user,
+                             @PathVariable Long noticeId) {
+        noticeService.deleteNotice(user, noticeId);
+    }
+
+    @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取公告")
+    @GetMapping("/course/{courseId}")
+    public PageResponse<NoticeVO> getNotices(LoginUser user,
+                                             @PathVariable Long courseId,
+                                             @RequestParam(defaultValue = "") String key,
+                                             @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
+        return PageResponse.of(noticeService.getNoticesByCourse(user, courseId, key, pageable));
+    }
+}

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

@@ -6,10 +6,9 @@ import nju.seec.helper.dto.ReplyDTO;
 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.*;
 
-import javax.validation.Valid;
-
 /**
  * @author cst
  */
@@ -28,7 +27,7 @@ public class ReplyController {
     @Auth(roles = UserType.TEACHER, message = "回复")
     @PostMapping
     public ReplyVO createReply(LoginUser user,
-                               @Valid @RequestBody ReplyDTO replyDTO) {
+                               @Validated @RequestBody ReplyDTO replyDTO) {
         return replyService.createReply(user, replyDTO);
     }
 
@@ -38,7 +37,7 @@ public class ReplyController {
     @Auth(roles = UserType.TEACHER, message = "删除回复")
     @DeleteMapping("/{replyId}")
     public void removeReply(LoginUser user,
-                            @PathVariable Integer replyId) {
+                            @PathVariable Long replyId) {
         replyService.removeReply(user, replyId);
     }
 }

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

@@ -3,21 +3,22 @@ package nju.seec.helper.controller;
 import nju.seec.helper.aspect.auth.Auth;
 import nju.seec.helper.controller.response.PageResponse;
 import nju.seec.helper.dto.LoginUser;
-import nju.seec.helper.dto.ModifySlideDTO;
 import nju.seec.helper.dto.SlideDTO;
+import nju.seec.helper.dto.SlideFileDTO;
 import nju.seec.helper.dto.SlideStateDTO;
+import nju.seec.helper.dto.groups.Create;
+import nju.seec.helper.dto.groups.Modify;
 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;
 import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import javax.validation.Valid;
-
 /**
- * 幻灯片
+ * 课件
  *
  * @author cst
  */
@@ -31,50 +32,60 @@ public class SlideController {
     }
 
     /**
-     * 教师创建幻灯片
+     * 教师创建课件
      */
-    @Auth(roles = UserType.TEACHER, message = "创建幻灯片")
+    @Auth(roles = UserType.TEACHER, message = "创建课件")
     @PostMapping
     public ResponseEntity<SlideVO> createSlide(LoginUser user,
-                                               @Valid SlideDTO slideDTO) {
+                                               @Validated(Create.class) SlideDTO slideDTO) {
         SlideVO slideVO = slideService.createSlide(user, slideDTO);
         return ResponseEntity.ok(slideVO);
     }
 
     /**
-     * 教师修改幻灯片
+     * 教师修改课件
      */
-    @Auth(roles = UserType.TEACHER, message = "修改幻灯片")
+    @Auth(roles = UserType.TEACHER, message = "修改课件")
     @PutMapping
     public SlideVO modifySlide(LoginUser user,
-                               @Valid @RequestBody ModifySlideDTO modifySlideDTO) {
-        return slideService.modifySlide(user, modifySlideDTO);
+                               @Validated(Modify.class) @RequestBody SlideDTO slideDTO) {
+        return slideService.modifySlide(user, slideDTO);
     }
 
     /**
-     * 教师修改幻灯片状态
+     * 教师修改课件状态
      */
-    @Auth(roles = UserType.TEACHER, message = "修改幻灯片状态")
+    @Auth(roles = UserType.TEACHER, message = "修改课件状态")
     @PutMapping("/state")
     public void modifySlideState(LoginUser user,
-                                 @Valid @RequestBody SlideStateDTO slideStateDTO) {
+                                 @Validated @RequestBody SlideStateDTO slideStateDTO) {
         slideService.modifySlideState(user, slideStateDTO);
     }
 
     /**
-     * 教师删除幻灯片
+     * 教师修改课件文件
+     */
+    @Auth(roles = UserType.TEACHER, message = "修改课件文件")
+    @PutMapping("/file")
+    public void modifySlideFile(LoginUser user,
+                                @Validated SlideFileDTO slideFileDTO) {
+        slideService.modifySlideFile(user, slideFileDTO);
+    }
+
+    /**
+     * 教师删除课件
      */
-    @Auth(roles = UserType.TEACHER, message = "删除幻灯片")
+    @Auth(roles = UserType.TEACHER, message = "删除课件")
     @DeleteMapping("/{slideId}")
     public void removeSlide(LoginUser user,
-                            @PathVariable Integer slideId) {
+                            @PathVariable Long slideId) {
         slideService.removeSlide(user, slideId);
     }
 
     /**
-     * 教师获取幻灯片
+     * 教师获取课件
      */
-    @Auth(roles = UserType.TEACHER, message = "获取幻灯片列表")
+    @Auth(roles = UserType.TEACHER, message = "获取课件")
     @GetMapping("/teacher")
     public PageResponse<SlideVO> teacherGetSlides(LoginUser user,
                                                   @RequestParam(required = false, defaultValue = "") String key,
@@ -83,36 +94,45 @@ public class SlideController {
     }
 
     /**
-     * 教师基于课程获取幻灯片
+     * 教师基于课程获取课件
      */
-    @Auth(roles = UserType.TEACHER, message = "获取幻灯片列表")
+    @Auth(roles = UserType.TEACHER, message = "获取课件")
     @GetMapping("/teacher/course/{courseId}")
     public PageResponse<SlideVO> teacherGetSlidesByCourse(LoginUser user,
-                                                          @PathVariable Integer courseId,
+                                                          @PathVariable Long courseId,
                                                           @RequestParam(required = false, defaultValue = "") String key,
                                                           @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
         return PageResponse.of(slideService.teacherGetSlidesByCourse(user, courseId, key, pageable));
     }
 
     /**
-     * 学生基于课程获取幻灯片
+     * 学生基于课程获取课件
      */
-    @Auth(roles = UserType.STUDENT, message = "获取幻灯片列表")
+    @Auth(roles = UserType.STUDENT, message = "获取课件")
     @GetMapping("/student/course/{courseId}")
     public PageResponse<SlideVO> studentGetSlidesByCourse(LoginUser user,
-                                                          @PathVariable Integer courseId,
+                                                          @PathVariable Long courseId,
                                                           @RequestParam(required = false, defaultValue = "") String key,
                                                           @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable) {
         return PageResponse.of(slideService.studentGetSlidesByCourse(user, courseId, key, pageable));
     }
 
     /**
-     * 获取幻灯片链接
+     * 获取课件链接
      */
-    @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取幻灯片链接")
+    @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取课件链接")
     @GetMapping("/{slideId}/url")
     public String getSlideUrl(LoginUser user,
-                              @PathVariable Integer slideId) {
+                              @PathVariable Long slideId) {
         return slideService.getSlideUrl(user, slideId);
     }
+
+    /**
+     * 获取某课件
+     */
+    @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "获取课件")
+    @GetMapping("/{slideId}")
+    public SlideVO getOneSlide(LoginUser user, @PathVariable Long slideId) {
+        return slideService.getOneSlide(user, slideId);
+    }
 }

+ 13 - 8
src/main/java/nju/seec/helper/controller/UserController.java

@@ -1,15 +1,20 @@
 package nju.seec.helper.controller;
 
 import nju.seec.helper.aspect.auth.Auth;
-import nju.seec.helper.dto.*;
+import nju.seec.helper.dto.LoginDTO;
+import nju.seec.helper.dto.LoginUser;
+import nju.seec.helper.dto.ResetPasswordDTO;
+import nju.seec.helper.dto.UserDTO;
+import nju.seec.helper.dto.groups.Create;
+import nju.seec.helper.dto.groups.Modify;
 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.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpSession;
-import javax.validation.Valid;
 
 /**
  * 用户
@@ -29,15 +34,15 @@ public class UserController {
      * 注册
      */
     @PostMapping("/register")
-    public UserVO register(@Valid @RequestBody RegisterDTO registerDTO) {
-        return userService.createUser(registerDTO);
+    public UserVO register(@Validated(Create.class) @RequestBody UserDTO userDTO) {
+        return userService.createUser(userDTO);
     }
 
     /**
      * 邮箱登录
      */
     @PostMapping("/login/email")
-    public UserVO loginByEmail(@Valid @RequestBody LoginDTO loginDTO,
+    public UserVO loginByEmail(@Validated @RequestBody LoginDTO loginDTO,
                                HttpSession session) {
         UserVO userVO = userService.getUserByEmailAndPassword(loginDTO.getUsername(), loginDTO.getPassword());
 
@@ -67,9 +72,9 @@ public class UserController {
     @Auth(roles = {UserType.TEACHER, UserType.STUDENT}, message = "修改个人信息")
     @PutMapping
     public UserVO modifyUser(LoginUser user,
-                             @Valid @RequestBody ModifyUserDTO modifyUserDTO,
+                             @Validated(Modify.class) @RequestBody UserDTO userDTO,
                              HttpSession session) {
-        UserVO userVO = userService.modifyUser(user, modifyUserDTO);
+        UserVO userVO = userService.modifyUser(user, userDTO);
         user = ((LoginUser) session.getAttribute(Consts.SESSION_USER_NAME))
                 .setId(userVO.getId())
                 .setName(userVO.getName())
@@ -82,7 +87,7 @@ public class UserController {
      * 重设密码
      */
     @PutMapping("/resetPassword/email")
-    public void resetPasswordByEmail(@Valid @RequestBody ResetPasswordDTO resetPasswordDTO) {
+    public void resetPasswordByEmail(@Validated @RequestBody ResetPasswordDTO resetPasswordDTO) {
         userService.resetPasswordByEmail(resetPasswordDTO);
     }
 

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

@@ -16,9 +16,7 @@ public class PageResponse<T> {
     private PageInfo page;
 
     public static <T> PageResponse<T> of(Page<T> page) {
-        return new PageResponse<>(page.getContent(),
-                PageInfo.of(page.getNumber(), page.getSize(),
-                        page.getTotalPages(), page.getNumberOfElements()));
+        return new PageResponse<>(page.getContent(), PageInfo.of(page.getNumber(), page.getSize(), page.getTotalPages(), page.getTotalElements()));
     }
 
     @Data
@@ -27,6 +25,6 @@ public class PageResponse<T> {
         private int number;
         private int size;
         private int totalPages;
-        private int totalElements;
+        private long totalElements;
     }
 }

+ 12 - 6
src/main/java/nju/seec/helper/dao/ChooseDAO.java

@@ -12,7 +12,7 @@ import java.util.Set;
  * @author cst
  */
 @Repository
-public interface ChooseDAO extends JpaRepository<Choose, Integer> {
+public interface ChooseDAO extends JpaRepository<Choose, Long> {
     /**
      * 检查学生的选课信息是否存在
      *
@@ -20,7 +20,7 @@ public interface ChooseDAO extends JpaRepository<Choose, Integer> {
      * @param courseId
      * @return
      */
-    boolean existsByStudentIdAndCourseId(Integer studentId, Integer courseId);
+    boolean existsByStudentIdAndCourseId(Long studentId, Long courseId);
 
     /**
      * 查询选课信息
@@ -29,7 +29,7 @@ public interface ChooseDAO extends JpaRepository<Choose, Integer> {
      * @param courseId
      * @return
      */
-    Optional<Choose> findByStudentIdAndCourseId(Integer studentId, Integer courseId);
+    Optional<Choose> findByStudentIdAndCourseId(Long studentId, Long courseId);
 
     /**
      * 查询学生选课信息
@@ -38,8 +38,14 @@ public interface ChooseDAO extends JpaRepository<Choose, Integer> {
      * @return
      */
     @Query("select distinct choose.courseId from Choose choose where choose.studentId=?1")
-    Set<Integer> findCourseIdsByStudentId(Integer studentId);
+    Set<Long> findCourseIdsByStudentId(Long studentId);
 
-    @Query("select distinct choose.studentId from Choose choose where choose.courseId=?1")
-    Set<Integer> findStudentIdsByCourseId(Integer courseId);
+    /**
+     * 查询课程的选课学生
+     *
+     * @param courseId
+     * @return
+     */
+    @Query("select distinct c.studentId from Choose c where c.courseId=?1")
+    Set<Long> findStudentIdsByCourseId(Long courseId);
 }

+ 21 - 5
src/main/java/nju/seec/helper/dao/CommentDAO.java

@@ -2,6 +2,7 @@ package nju.seec.helper.dao;
 
 import nju.seec.helper.entity.Comment;
 import nju.seec.helper.util.enums.ExceptionType;
+import nju.seec.helper.util.enums.UserType;
 import nju.seec.helper.util.exception.HelperException;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -15,14 +16,14 @@ import org.springframework.transaction.annotation.Transactional;
  * @author cst
  */
 @Repository
-public interface CommentDAO extends JpaRepository<Comment, Integer> {
+public interface CommentDAO extends JpaRepository<Comment, Long> {
     /**
      * 封装findById
      *
      * @param id
      * @return
      */
-    default Comment findCommentById(Integer id) {
+    default Comment findCommentById(Long id) {
         return this.findById(id)
                 .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到评论"));
     }
@@ -38,7 +39,7 @@ public interface CommentDAO extends JpaRepository<Comment, Integer> {
             "where c.slideId=?1 " +
             "and c.pageNumber=?2 " +
             "and c.topNumber is not null")
-    int countTopComments(Integer slideId, Integer pageNumber);
+    int countTopComments(Long slideId, Integer pageNumber);
 
     /**
      * 获取评论
@@ -52,7 +53,7 @@ public interface CommentDAO extends JpaRepository<Comment, Integer> {
             "where c.slideId=?1 " +
             "and c.pageNumber=?2 " +
             "order by nullif(c.topNumber, 0) desc")
-    Page<Comment> findComments(Integer slideId, Integer pageNumber, Pageable pageable);
+    Page<Comment> findBySlideIdAndPageNumber(Long slideId, Integer pageNumber, Pageable pageable);
 
     /**
      * 置顶减一
@@ -69,5 +70,20 @@ public interface CommentDAO extends JpaRepository<Comment, Integer> {
             "and c.pageNumber=?2 " +
             "and c.topNumber is not null " +
             "and c.topNumber>?3")
-    void decTopNumber(Integer slideId, Integer pageNumber, Integer topNumber);
+    void decTopNumber(Long slideId, Integer pageNumber, Integer topNumber);
+
+    /**
+     * 更新所有人信息
+     *
+     * @param userId
+     * @param userName
+     * @param userType
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Modifying
+    @Query("update Comment c " +
+            "set c.userName=?2, " +
+            "c.userType=?3 " +
+            "where c.userId=?1 ")
+    void updateUser(Long userId, String userName, UserType userType);
 }

+ 25 - 9
src/main/java/nju/seec/helper/dao/CourseDAO.java

@@ -6,8 +6,10 @@ import nju.seec.helper.util.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.Modifying;
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.Set;
 
@@ -15,15 +17,16 @@ import java.util.Set;
  * @author cst
  */
 @Repository
-public interface CourseDAO extends JpaRepository<Course, Integer> {
+public interface CourseDAO extends JpaRepository<Course, Long> {
     /**
      * 封装findById
      *
      * @param id
      * @return
      */
-    default Course findCourseById(Integer id) {
+    default Course findCourseById(Long id) {
         return this.findById(id)
+                .filter(course -> course.getDeleteAt() == 0)
                 .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到课程"));
     }
 
@@ -37,11 +40,11 @@ public interface CourseDAO extends JpaRepository<Course, Integer> {
      */
     @Query("select case when count(c.id)>0 then true else false end " +
             "from Course c " +
-            "where c.teacherId=?1 " +
-            "and c.name=?2 " +
-            "and c.deleteAt=0 " +
-            "and c.id <> ?3 ")
-    boolean existsByTeacherIdAndName(Integer teacherId, String name, Integer courseId);
+            "where c.id<>?1 " +
+            "and c.teacherId=?2 " +
+            "and c.name=?3 " +
+            "and c.deleteAt=0 ")
+    boolean existsByTeacherIdAndName(Long courseId, Long teacherId, String name);
 
     /**
      * 查询老师创建的课程
@@ -55,7 +58,7 @@ public interface CourseDAO extends JpaRepository<Course, Integer> {
             "where c.teacherId=?1 " +
             "and c.name like concat('%',?2,'%') " +
             "and c.deleteAt=0 ")
-    Page<Course> findByTeacherIdAndKey(Integer teacherId, String key, Pageable pageable);
+    Page<Course> findByTeacherIdAndKey(Long teacherId, String key, Pageable pageable);
 
     /**
      * 查询课程
@@ -81,5 +84,18 @@ public interface CourseDAO extends JpaRepository<Course, Integer> {
             "where c.id in ?1 " +
             "and (c.name like concat('%',?2,'%') or c.teacherName like concat('%',?2,'%')) " +
             "and c.deleteAt=0 ")
-    Page<Course> findByIdsAndKey(Set<Integer> courseIds, String key, Pageable pageable);
+    Page<Course> findByIdsAndKey(Set<Long> courseIds, String key, Pageable pageable);
+
+    /**
+     * 更新所有人信息
+     *
+     * @param userId
+     * @param userName
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Modifying
+    @Query("update Course c " +
+            "set c.teacherName=?2 " +
+            "where c.teacherId=?1 ")
+    void updateUser(Long userId, String userName);
 }

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

@@ -0,0 +1,56 @@
+package nju.seec.helper.dao;
+
+import nju.seec.helper.entity.Message;
+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.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Set;
+
+/**
+ * @author cst
+ */
+@Repository
+public interface MessageDAO extends JpaRepository<Message, Long> {
+    /**
+     * 更新消息已读
+     *
+     * @param toUserId
+     * @param messageIds
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Modifying
+    @Query("update Message m " +
+            "set m.read=true " +
+            "where m.toUserId=?1 " +
+            "and m.id in ?2 ")
+    void updateRead(Long toUserId, Set<Long> messageIds);
+
+    /**
+     * 删除消息
+     *
+     * @param toUserId
+     * @param messageIds
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Modifying
+    @Query("delete from Message m " +
+            "where m.toUserId=?1 " +
+            "and m.id in ?2 ")
+    void delete(Long toUserId, Set<Long> messageIds);
+
+    /**
+     * 获取消息
+     *
+     * @param toUserId
+     * @param read
+     * @param key
+     * @param pageable
+     * @return
+     */
+    Page<Message> findByToUserIdAndReadAndContentContains(Long toUserId, Boolean read, String key, Pageable pageable);
+}

+ 40 - 0
src/main/java/nju/seec/helper/dao/NoticeDAO.java

@@ -0,0 +1,40 @@
+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 org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+/**
+ * @author cst
+ */
+@Repository
+public interface NoticeDAO extends JpaRepository<Notice, Long> {
+    /**
+     * 封装findById
+     *
+     * @param id
+     * @return
+     */
+    default Notice findNoticeById(Long id) {
+        return this.findById(id)
+                .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到公告"));
+    }
+
+    /**
+     * 根据课程ID查找
+     *
+     * @param courseId
+     * @param key
+     * @param pageable
+     * @return
+     */
+    @Query("select n from Notice n " +
+            "where n.courseId=?1 " +
+            "and (n.title like concat('%',?2,'%') or n.content like concat('%',?2,'%') )")
+    Page<Notice> findByCourseIdAndKey(Long courseId, String key, Pageable pageable);
+}

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

@@ -10,14 +10,14 @@ import org.springframework.stereotype.Repository;
  * @author cst
  */
 @Repository
-public interface ReplyDAO extends JpaRepository<Reply, Integer> {
+public interface ReplyDAO extends JpaRepository<Reply, Long> {
     /**
      * 封装findById
      *
      * @param id
      * @return
      */
-    default Reply findReplyById(Integer id) {
+    default Reply findReplyById(Long id) {
         return this.findById(id)
                 .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到回复"));
     }

+ 54 - 9
src/main/java/nju/seec/helper/dao/SlideDAO.java

@@ -7,8 +7,10 @@ import nju.seec.helper.util.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.Modifying;
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.Set;
 
@@ -16,20 +18,21 @@ import java.util.Set;
  * @author cst
  */
 @Repository
-public interface SlideDAO extends JpaRepository<Slide, Integer> {
+public interface SlideDAO extends JpaRepository<Slide, Long> {
     /**
      * 封装findById
      *
      * @param id
      * @return
      */
-    default Slide findSlideById(Integer id) {
+    default Slide findSlideById(Long id) {
         return this.findById(id)
-                .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到幻灯片"));
+                .filter(slide -> slide.getDeleteAt() == 0)
+                .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到课件"));
     }
 
     /**
-     * 查询教师创建的幻灯片
+     * 查询教师创建的课件
      *
      * @param teacherId
      * @param key
@@ -40,10 +43,10 @@ public interface SlideDAO extends JpaRepository<Slide, Integer> {
             "where s.teacherId=?1 " +
             "and s.name like concat('%',?2,'%') " +
             "and s.deleteAt=0")
-    Page<Slide> findByTeacherIdAndNameContains(Integer teacherId, String key, Pageable pageable);
+    Page<Slide> findByTeacherIdAndNameContains(Long teacherId, String key, Pageable pageable);
 
     /**
-     * 查询教师创建的该课程的幻灯片
+     * 查询教师创建的该课程的课件
      *
      * @param courseId
      * @param teacherId
@@ -56,10 +59,10 @@ public interface SlideDAO extends JpaRepository<Slide, Integer> {
             "and s.teacherId=?2 " +
             "and s.name like concat('%',?3,'%') " +
             "and s.deleteAt=0")
-    Page<Slide> findByCourseIdAndTeacherIdAndNameContains(Integer courseId, Integer teacherId, String key, Pageable pageable);
+    Page<Slide> findByCourseIdAndTeacherIdAndNameContains(Long courseId, Long teacherId, String key, Pageable pageable);
 
     /**
-     * 根据课程和幻灯片状态查询
+     * 根据课程和课件状态查询
      *
      * @param courseId
      * @param exclusiveStates
@@ -72,5 +75,47 @@ public interface SlideDAO extends JpaRepository<Slide, Integer> {
             "and s.state not in ?2 " +
             "and s.name like concat('%',?3,'%') " +
             "and s.deleteAt=0")
-    Page<Slide> findByCourseIdAndStateNotInAndNameContains(Integer courseId, Set<SlideState> exclusiveStates, String key, Pageable pageable);
+    Page<Slide> findByCourseIdAndStateNotInAndNameContains(Long courseId, Set<SlideState> exclusiveStates, String key, Pageable pageable);
+
+    /**
+     * 更新所有人信息
+     *
+     * @param userId
+     * @param userName
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Modifying
+    @Query("update Slide s " +
+            "set s.teacherName=?2 " +
+            "where s.teacherId=?1 ")
+    void updateUser(Long userId, String userName);
+
+    /**
+     * 更新课程信息
+     *
+     * @param courseId
+     * @param courseName
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Modifying
+    @Query("update Slide s " +
+            "set s.courseName=?2 " +
+            "where s.courseId=?1 ")
+    void updateCourse(Long courseId, String courseName);
+
+    /**
+     * 检查同名课件
+     *
+     * @param slideId
+     * @param courseId
+     * @param name
+     * @return
+     */
+    @Query("select case when count(s.id)>0 then true else false end " +
+            "from Slide s " +
+            "where s.id<>?1 " +
+            "and s.courseId=?2 " +
+            "and s.name=?3 " +
+            "and s.deleteAt=0 ")
+    boolean existsByCourseIdAndName(Long slideId, Long courseId, String name);
 }

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

@@ -12,14 +12,14 @@ import java.util.Optional;
  * @author cst
  */
 @Repository
-public interface UserDAO extends JpaRepository<User, Integer> {
+public interface UserDAO extends JpaRepository<User, Long> {
     /**
      * 封装findById
      *
      * @param id
      * @return
      */
-    default User findUserById(Integer id) {
+    default User findUserById(Long id) {
         return this.findById(id)
                 .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到用户信息"));
     }

+ 6 - 4
src/main/java/nju/seec/helper/dto/ChooseDTO.java

@@ -4,14 +4,16 @@ import lombok.Data;
 
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
+import java.io.Serializable;
 
 /**
  * @author cst
  */
 @Data
-public class ChooseDTO {
-    @NotNull
-    private Integer courseId;
-    @NotBlank
+public class ChooseDTO implements Serializable {
+    private static final long serialVersionUID = -5102844543057338777L;
+    @NotNull(message = "缺少课程ID")
+    private Long courseId;
+    @NotBlank(message = "选课码不能为空")
     private String code;
 }

+ 11 - 6
src/main/java/nju/seec/helper/dto/CommentDTO.java

@@ -1,21 +1,26 @@
 package nju.seec.helper.dto;
 
 import lombok.Data;
+import org.hibernate.validator.constraints.Length;
 
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
+import java.io.Serializable;
 
 /**
  * @author cst
  */
 @Data
-public class CommentDTO {
-    @NotNull
-    private Integer slideId;
-    @NotNull
+public class CommentDTO implements Serializable {
+    private static final long serialVersionUID = 5433422677493471323L;
+    @NotNull(message = "缺少课件ID")
+    private Long slideId;
+    @NotNull(message = "缺少页码")
     private Integer pageNumber;
-    @NotBlank
+    @NotNull
+    @Length(max = 50, message = "标题长度不能超过50")
     private String title;
-    @NotBlank
+    @NotBlank(message = "内容不能为空")
+    @Length(max = 1000, message = "内容长度不能超过1000")
     private String content;
 }

+ 11 - 2
src/main/java/nju/seec/helper/dto/CourseDTO.java

@@ -1,19 +1,28 @@
 package nju.seec.helper.dto;
 
 import lombok.Data;
+import nju.seec.helper.dto.groups.Modify;
+import org.hibernate.validator.constraints.Length;
 
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
+import java.io.Serializable;
 
 /**
  * @author cst
  */
 @Data
-public class CourseDTO {
-    @NotBlank
+public class CourseDTO implements Serializable {
+    private static final long serialVersionUID = 4747833899049996472L;
+    @NotNull(message = "缺少课程ID", groups = Modify.class)
+    private Long id;
+    @NotBlank(message = "课程名不能为空")
+    @Length(max = 50, message = "课程名长度不能超过50")
     private String name;
     @NotNull
+    @Length(max = 200, message = "课程简介长度不能超过200")
     private String bio;
     @NotBlank
+    @Length(max = 50, message = "选课码长度不能超过50")
     private String code;
 }

+ 7 - 6
src/main/java/nju/seec/helper/dto/LoginDTO.java

@@ -1,19 +1,20 @@
 package nju.seec.helper.dto;
 
 import lombok.Data;
+import nju.seec.helper.dto.validator.schoolemail.SchoolEmail;
 
 import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
 
 /**
  * @author cst
  */
 @Data
-public class LoginDTO {
-    /**
-     * 邮箱或手机号
-     */
-    @NotBlank
+public class LoginDTO implements Serializable {
+    private static final long serialVersionUID = -3359173125836901241L;
+    @NotBlank(message = "邮箱不能为空")
+    @SchoolEmail
     private String username;
-    @NotBlank
+    @NotBlank(message = "密码不能为空")
     private String password;
 }

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

@@ -10,7 +10,7 @@ import nju.seec.helper.util.enums.UserType;
 @Data
 @Accessors(chain = true)
 public class LoginUser {
-    private Integer id;
+    private Long id;
     private String name;
     private UserType type;
 }

+ 17 - 0
src/main/java/nju/seec/helper/dto/MessageDTO.java

@@ -0,0 +1,17 @@
+package nju.seec.helper.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+ * @author cst
+ */
+@Data
+public class MessageDTO implements Serializable {
+    private static final long serialVersionUID = -4467691441630551772L;
+    @NotEmpty(message = "消息列表不能为空")
+    private Set<Long> ids;
+}

+ 0 - 23
src/main/java/nju/seec/helper/dto/ModifyCourseDTO.java

@@ -1,23 +0,0 @@
-package nju.seec.helper.dto;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
-
-/**
- * @author cst
- */
-@Data
-public class ModifyCourseDTO {
-    @NotNull
-    private Integer id;
-    @NotBlank
-    private String name;
-    @NotNull
-    private String bio;
-    @NotBlank
-    private String code;
-
-    private String cover;
-}

+ 0 - 17
src/main/java/nju/seec/helper/dto/ModifySlideDTO.java

@@ -1,17 +0,0 @@
-package nju.seec.helper.dto;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
-
-/**
- * @author cst
- */
-@Data
-public class ModifySlideDTO {
-    @NotNull
-    private Integer id;
-    @NotBlank
-    private String name;
-}

+ 0 - 16
src/main/java/nju/seec/helper/dto/ModifyUserDTO.java

@@ -1,16 +0,0 @@
-package nju.seec.helper.dto;
-
-import lombok.Data;
-import org.hibernate.validator.constraints.Length;
-
-import javax.validation.constraints.NotBlank;
-
-/**
- * @author cst
- */
-@Data
-public class ModifyUserDTO {
-    @NotBlank
-    @Length(min = 2)
-    private String name;
-}

+ 27 - 0
src/main/java/nju/seec/helper/dto/NoticeDTO.java

@@ -0,0 +1,27 @@
+package nju.seec.helper.dto;
+
+import lombok.Data;
+import nju.seec.helper.dto.groups.Create;
+import nju.seec.helper.dto.groups.Modify;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author cst
+ */
+@Data
+public class NoticeDTO {
+    @NotNull(message = "缺少公告ID", groups = Modify.class)
+    private Long id;
+    @NotNull(message = "缺少课程ID", groups = Create.class)
+    private Long courseId;
+    @NotBlank(message = "标题不能为空")
+    @Length(max = 50, message = "标题长度不能超过50")
+    private String title;
+
+    @NotBlank(message = "内容不能为空")
+    @Length(max = 1000, message = "内容长度不能超过1000")
+    private String content;
+}

+ 0 - 33
src/main/java/nju/seec/helper/dto/RegisterDTO.java

@@ -1,33 +0,0 @@
-package nju.seec.helper.dto;
-
-import lombok.Data;
-import nju.seec.helper.util.Consts;
-import org.hibernate.validator.constraints.Length;
-
-import javax.validation.constraints.Email;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
-import javax.validation.constraints.Pattern;
-
-/**
- * @author cst
- */
-@Data
-public class RegisterDTO {
-    @NotBlank(message = "姓名不能为空")
-    @Length(min = 2)
-    private String name;
-    @Email(message = "邮箱格式错误")
-    @Pattern(regexp = Consts.SCHOOL_EMAIL_REGEX, message = "该邮箱不是南大校邮")
-    private String email;
-    @NotBlank(message = "验证码不能为空")
-    private String emailCode;
-    @NotBlank
-    @Pattern(regexp = Consts.PHONE_REGEX, message = "手机号格式错误")
-    private String phone;
-    @NotNull
-    private String phoneCode;
-
-    @NotBlank(message = "密码不能为空")
-    private String password;
-}

+ 8 - 4
src/main/java/nju/seec/helper/dto/ReplyDTO.java

@@ -1,17 +1,21 @@
 package nju.seec.helper.dto;
 
 import lombok.Data;
+import org.hibernate.validator.constraints.Length;
 
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
+import java.io.Serializable;
 
 /**
  * @author cst
  */
 @Data
-public class ReplyDTO {
-    @NotNull
-    private Integer commentId;
-    @NotBlank
+public class ReplyDTO implements Serializable {
+    private static final long serialVersionUID = -8012723244114871873L;
+    @NotNull(message = "缺少评论ID")
+    private Long commentId;
+    @NotBlank(message = "内容不能为空")
+    @Length(max = 250, message = "内容长度不能超过250")
     private String content;
 }

+ 10 - 7
src/main/java/nju/seec/helper/dto/ResetPasswordDTO.java

@@ -1,27 +1,30 @@
 package nju.seec.helper.dto;
 
 import lombok.Data;
+import nju.seec.helper.dto.validator.schoolemail.SchoolEmail;
+import org.hibernate.validator.constraints.Length;
 
 import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
 
 /**
  * @author cst
  */
 @Data
-public class ResetPasswordDTO {
-    /**
-     * 邮箱或手机号
-     */
-    @NotBlank
+public class ResetPasswordDTO implements Serializable {
+    private static final long serialVersionUID = 3884011149489695206L;
+    @NotBlank(message = "邮箱不能为空")
+    @SchoolEmail
     private String username;
     /**
      * 验证码
      */
-    @NotBlank
+    @NotBlank(message = "邮箱验证码不能为空")
     private String code;
     /**
      * 密码
      */
-    @NotBlank
+    @NotBlank(message = "密码不能为空")
+    @Length(max = 50, message = "密码长度不能超过50位")
     private String password;
 }

+ 16 - 5
src/main/java/nju/seec/helper/dto/SlideDTO.java

@@ -1,20 +1,31 @@
 package nju.seec.helper.dto;
 
 import lombok.Data;
+import nju.seec.helper.dto.groups.Create;
+import nju.seec.helper.dto.groups.Modify;
+import nju.seec.helper.util.enums.SlideState;
+import org.hibernate.validator.constraints.Length;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
+import java.io.Serializable;
 
 /**
  * @author cst
  */
 @Data
-public class SlideDTO {
-    @NotNull
-    private Integer courseId;
-    @NotBlank
+public class SlideDTO implements Serializable {
+    private static final long serialVersionUID = -4127324852405823476L;
+    @NotNull(message = "缺少课件ID", groups = Modify.class)
+    private Long id;
+    @NotNull(message = "缺少课程ID", groups = Create.class)
+    private Long courseId;
+    @NotBlank(message = "课件名不能为空")
+    @Length(max = 50, message = "课件名长度不能超过50")
     private String name;
-    @NotNull
+    @NotNull(message = "缺少状态", groups = Create.class)
+    private SlideState state;
+    @NotNull(message = "缺少文件", groups = Create.class)
     private MultipartFile file;
 }

+ 19 - 0
src/main/java/nju/seec/helper/dto/SlideFileDTO.java

@@ -0,0 +1,19 @@
+package nju.seec.helper.dto;
+
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * @author cst
+ */
+@Data
+public class SlideFileDTO implements Serializable {
+    private static final long serialVersionUID = -3477483332353577715L;
+    @NotNull(message = "缺少课件ID")
+    private Long id;
+    @NotNull(message = "缺少文件")
+    private MultipartFile file;
+}

+ 6 - 4
src/main/java/nju/seec/helper/dto/SlideStateDTO.java

@@ -4,14 +4,16 @@ import lombok.Data;
 import nju.seec.helper.util.enums.SlideState;
 
 import javax.validation.constraints.NotNull;
+import java.io.Serializable;
 
 /**
  * @author cst
  */
 @Data
-public class SlideStateDTO {
-    @NotNull
-    private Integer id;
-    @NotNull
+public class SlideStateDTO implements Serializable {
+    private static final long serialVersionUID = 8163158733090757629L;
+    @NotNull(message = "缺少课件ID")
+    private Long id;
+    @NotNull(message = "缺少状态")
     private SlideState state;
 }

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

@@ -0,0 +1,34 @@
+package nju.seec.helper.dto;
+
+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 org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+/**
+ * @author cst
+ */
+@Data
+public class UserDTO implements Serializable {
+    private static final long serialVersionUID = -5962331511813309322L;
+    @NotBlank(message = "姓名不能为空")
+    @Length(min = 2, max = 50, message = "姓名长度必须在2到50之间")
+    private String name;
+    @NotBlank(message = "邮箱不能为空", groups = Create.class)
+    @SchoolEmail(groups = Create.class)
+    private String email;
+    @NotBlank(message = "邮箱验证码不能为空", groups = Create.class)
+    private String emailCode;
+    @NotBlank(message = "手机号不能为空", groups = Create.class)
+    @Phone(groups = Create.class)
+    private String phone;
+    @NotBlank(message = "手机号验证码不能为空", groups = Create.class)
+    private String phoneCode;
+    @NotBlank(message = "密码不能为空", groups = Create.class)
+    @Length(max = 50, message = "密码长度不能超过50位", groups = Create.class)
+    private String password;
+}

+ 9 - 0
src/main/java/nju/seec/helper/dto/groups/Create.java

@@ -0,0 +1,9 @@
+package nju.seec.helper.dto.groups;
+
+import javax.validation.groups.Default;
+
+/**
+ * @author cst
+ */
+public interface Create extends Default {
+}

+ 9 - 0
src/main/java/nju/seec/helper/dto/groups/Modify.java

@@ -0,0 +1,9 @@
+package nju.seec.helper.dto.groups;
+
+import javax.validation.groups.Default;
+
+/**
+ * @author cst
+ */
+public interface Modify extends Default {
+}

+ 20 - 0
src/main/java/nju/seec/helper/dto/validator/phone/Phone.java

@@ -0,0 +1,20 @@
+package nju.seec.helper.dto.validator.phone;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+/**
+ * @author cst
+ */
+@Documented
+@Target({ElementType.PARAMETER, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = PhoneValidator.class)
+public @interface Phone {
+    String message() default "手机号格式错误";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}

+ 21 - 0
src/main/java/nju/seec/helper/dto/validator/phone/PhoneValidator.java

@@ -0,0 +1,21 @@
+package nju.seec.helper.dto.validator.phone;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+/**
+ * @author cst
+ */
+public class PhoneValidator implements ConstraintValidator<Phone, String> {
+    private static final String PHONE_REGEX = "1[3-9]\\d{9}";
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext context) {
+        return value != null && value.matches(PHONE_REGEX);
+    }
+
+    @Override
+    public void initialize(Phone constraintAnnotation) {
+
+    }
+}

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

@@ -0,0 +1,32 @@
+package nju.seec.helper.dto.validator.schoolemail;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import javax.validation.constraints.Email;
+import javax.validation.constraints.Pattern;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * @author cst
+ * copy from {@link Email}
+ */
+@Documented
+@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
+@Retention(RUNTIME)
+@Constraint(validatedBy = SchoolEmailValidator.class)
+public @interface SchoolEmail {
+    String message() default "该邮箱不是南大校邮";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+
+    String regexp() default ".+@(nju.edu.cn|smail.nju.edu.cn)";
+
+    Pattern.Flag[] flags() default {};
+}

+ 54 - 0
src/main/java/nju/seec/helper/dto/validator/schoolemail/SchoolEmailValidator.java

@@ -0,0 +1,54 @@
+package nju.seec.helper.dto.validator.schoolemail;
+
+import org.hibernate.validator.internal.constraintvalidators.AbstractEmailValidator;
+import org.hibernate.validator.internal.util.logging.Log;
+import org.hibernate.validator.internal.util.logging.LoggerFactory;
+
+import javax.validation.ConstraintValidatorContext;
+import java.lang.invoke.MethodHandles;
+import java.util.regex.Matcher;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * @author cst
+ * copy from {@link org.hibernate.validator.internal.constraintvalidators.bv.EmailValidator}
+ */
+public class SchoolEmailValidator extends AbstractEmailValidator<SchoolEmail> {
+    private static final Log LOG = LoggerFactory.make(MethodHandles.lookup());
+    private java.util.regex.Pattern pattern;
+
+    @Override
+    public void initialize(SchoolEmail emailAnnotation) {
+        super.initialize(emailAnnotation);
+
+        javax.validation.constraints.Pattern.Flag[] flags = emailAnnotation.flags();
+        int intFlag = 0;
+        for (javax.validation.constraints.Pattern.Flag flag : flags) {
+            intFlag = intFlag | flag.getValue();
+        }
+
+        // we only apply the regexp if there is one to apply
+        if (!".*".equals(emailAnnotation.regexp()) || emailAnnotation.flags().length > 0) {
+            try {
+                pattern = java.util.regex.Pattern.compile(emailAnnotation.regexp(), intFlag);
+            } catch (PatternSyntaxException e) {
+                throw LOG.getInvalidRegularExpressionException(e);
+            }
+        }
+    }
+
+    @Override
+    public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
+        if (value == null) {
+            return true;
+        }
+
+        boolean isValid = super.isValid(value, context);
+        if (pattern == null || !isValid) {
+            return isValid;
+        }
+
+        Matcher m = pattern.matcher(value);
+        return m.matches();
+    }
+}

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

@@ -11,15 +11,16 @@ import javax.persistence.*;
 @Data
 @Accessors(chain = true)
 @Entity
-@Table(name = "choose")
+@Table(name = "choose",
+        uniqueConstraints = @UniqueConstraint(columnNames = {"course_id", "student_id"}))
 public class Choose {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
-    private Integer id;
+    private Long id;
 
     @Column(name = "course_id", nullable = false)
-    private Integer courseId;
+    private Long courseId;
 
     @Column(name = "student_id", nullable = false)
-    private Integer studentId;
+    private Long studentId;
 }

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

@@ -4,6 +4,8 @@ import lombok.Data;
 import lombok.experimental.Accessors;
 import nju.seec.helper.util.enums.UserType;
 import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
 
 import javax.persistence.*;
 import java.time.LocalDateTime;
@@ -15,17 +17,22 @@ import java.time.LocalDateTime;
 @Accessors(chain = true)
 @Entity
 @Table(name = "comment")
+@DynamicUpdate
+@DynamicInsert
 public class Comment {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
-    private Integer id;
+    private Long id;
 
     @Column(name = "slide_id", nullable = false)
-    private Integer slideId;
+    private Long slideId;
 
     @Column(name = "page_number", nullable = false)
     private Integer pageNumber;
 
+    @Column(name = "`show`", nullable = false, columnDefinition = "bit(1) default true")
+    private Boolean show = true;
+
     @Column(name = "top_number")
     private Integer topNumber;
 
@@ -36,7 +43,7 @@ public class Comment {
     private String content;
 
     @Column(name = "user_id", nullable = false)
-    private Integer userId;
+    private Long userId;
 
     @Column(name = "user_name", nullable = false)
     private String userName;
@@ -45,7 +52,7 @@ public class Comment {
     @Column(name = "user_type", nullable = false)
     private UserType userType;
 
-    @Column(name = "create_at", updatable = false)
+    @Column(name = "create_at", updatable = false, nullable = false)
     @CreationTimestamp
     private LocalDateTime createAt;
 

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

@@ -3,6 +3,7 @@ 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 javax.persistence.*;
@@ -17,10 +18,11 @@ import java.time.LocalDateTime;
 @Table(name = "course",
         uniqueConstraints = @UniqueConstraint(name = "课程名唯一约束", columnNames = {"teacher_id", "name", "delete_at"}))
 @DynamicUpdate
+@DynamicInsert
 public class Course {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
-    private Integer id;
+    private Long id;
 
     @Column(nullable = false)
     private String name;
@@ -36,7 +38,7 @@ public class Course {
     private LocalDateTime createAt;
 
     @Column(name = "teacher_id", nullable = false)
-    private Integer teacherId;
+    private Long teacherId;
 
     /**
      * 冗余信息,有利于课程搜索和信息展示

+ 47 - 0
src/main/java/nju/seec/helper/entity/Message.java

@@ -0,0 +1,47 @@
+package nju.seec.helper.entity;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import nju.seec.helper.util.enums.MessageType;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+/**
+ * @author cst
+ */
+@Data
+@Accessors(chain = true)
+@Entity
+@Table(name = "message")
+@Inheritance(strategy = InheritanceType.JOINED)
+@DynamicUpdate
+@DynamicInsert
+public class Message {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @Enumerated(EnumType.STRING)
+    @Column(nullable = false)
+    private MessageType type;
+
+    @Column(name = "to_user_id", nullable = false)
+    private Long toUserId;
+
+    @Column(name = "slide_id")
+    private Long slideId;
+
+    @Column(nullable = false)
+    private String content;
+
+    @Column(name = "`read`", columnDefinition = "bit(1) default false not null")
+    private Boolean read = false;
+
+    @Column(name = "create_at", nullable = false, updatable = false)
+    @CreationTimestamp
+    private LocalDateTime createAt;
+}

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

@@ -0,0 +1,43 @@
+package nju.seec.helper.entity;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+/**
+ * @author cst
+ */
+@Data
+@Accessors(chain = true)
+@Entity
+@Table(name = "notice")
+public class Notice {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @Column(name = "course_id", nullable = false)
+    private Long courseId;
+
+    @Column(name = "teacher_id", nullable = false)
+    private Long teacherId;
+
+    @Column(nullable = false)
+    private String title;
+
+    @Lob
+    @Column(columnDefinition = "text", nullable = false)
+    private String content;
+
+    @Column(name = "create_at", nullable = false, updatable = false)
+    @CreationTimestamp
+    private LocalDateTime createAt;
+
+    @Column(name = "update_at", nullable = false)
+    @UpdateTimestamp
+    private LocalDateTime updateAt;
+}

+ 7 - 3
src/main/java/nju/seec/helper/entity/Reply.java

@@ -3,6 +3,8 @@ 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 javax.persistence.*;
 import java.time.LocalDateTime;
@@ -14,22 +16,24 @@ import java.time.LocalDateTime;
 @Accessors(chain = true)
 @Entity
 @Table(name = "reply")
+@DynamicUpdate
+@DynamicInsert
 public class Reply {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
-    private Integer id;
+    private Long id;
 
     @OneToOne
     @JoinColumn(name = "comment_id", nullable = false)
     private Comment comment;
 
     @Column(name = "teacher_id", nullable = false)
-    private Integer teacherId;
+    private Long teacherId;
 
     @Column(nullable = false)
     private String content;
 
-    @Column(name = "create_at", updatable = false)
+    @Column(name = "create_at", updatable = false, nullable = false)
     @CreationTimestamp
     private LocalDateTime createAt;
 }

+ 9 - 4
src/main/java/nju/seec/helper/entity/Slide.java

@@ -4,6 +4,8 @@ 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 javax.persistence.*;
@@ -15,20 +17,23 @@ import java.time.LocalDateTime;
 @Data
 @Accessors(chain = true)
 @Entity
-@Table(name = "slide")
+@Table(name = "slide",
+        uniqueConstraints = @UniqueConstraint(name = "课件名唯一约束", columnNames = {"course_id", "name", "delete_at"}))
+@DynamicUpdate
+@DynamicInsert
 public class Slide {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
-    private Integer id;
+    private Long id;
 
     @Column(name = "course_id", nullable = false)
-    private Integer courseId;
+    private Long courseId;
 
     @Column(name = "course_name", nullable = false)
     private String courseName;
 
     @Column(name = "teacher_id", nullable = false)
-    private Integer teacherId;
+    private Long teacherId;
 
     @Column(name = "teacher_name", nullable = false)
     private String teacherName;

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

@@ -4,6 +4,8 @@ import lombok.Data;
 import lombok.experimental.Accessors;
 import nju.seec.helper.util.enums.UserType;
 import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
 
 import javax.persistence.*;
 import java.time.LocalDateTime;
@@ -15,10 +17,12 @@ import java.time.LocalDateTime;
 @Accessors(chain = true)
 @Entity
 @Table(name = "user")
+@DynamicUpdate
+@DynamicInsert
 public class User {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
-    private Integer id;
+    private Long id;
 
     @Column(nullable = false)
     private String name;
@@ -36,7 +40,7 @@ public class User {
     @Column(nullable = false)
     private String password;
 
-    @Column(name = "create_at", updatable = false)
+    @Column(name = "create_at", updatable = false, nullable = false)
     @CreationTimestamp
     private LocalDateTime createAt;
 }

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

@@ -25,7 +25,7 @@ public interface CommentService {
      * @param user
      * @param commentId
      */
-    void removeComment(LoginUser user, Integer commentId);
+    void removeComment(LoginUser user, Long commentId);
 
     /**
      * 置顶评论
@@ -33,7 +33,7 @@ public interface CommentService {
      * @param user
      * @param commentId
      */
-    void topComment(LoginUser user, Integer commentId);
+    void topComment(LoginUser user, Long commentId);
 
     /**
      * 取消置顶
@@ -41,15 +41,23 @@ public interface CommentService {
      * @param user
      * @param commentId
      */
-    void unTopComment(LoginUser user, Integer commentId);
+    void unTopComment(LoginUser user, Long commentId);
 
     /**
-     * 基于幻灯片和页码数获取评论
+     * 基于课件和页码数获取评论
      *
      * @param slideId
      * @param pageNumber
      * @param pageable
      * @return
      */
-    Page<CommentVO> getCommentsBySlideIdAndPageNumber(Integer slideId, Integer pageNumber, Pageable pageable);
+    Page<CommentVO> getCommentsBySlideAndPageNumber(Long slideId, Integer pageNumber, Pageable pageable);
+
+    /**
+     * 获得某条评论
+     *
+     * @param commentId
+     * @return
+     */
+    CommentVO getOneComment(Long commentId);
 }

+ 21 - 5
src/main/java/nju/seec/helper/service/CourseService.java

@@ -3,7 +3,6 @@ package nju.seec.helper.service;
 import nju.seec.helper.dto.ChooseDTO;
 import nju.seec.helper.dto.CourseDTO;
 import nju.seec.helper.dto.LoginUser;
-import nju.seec.helper.dto.ModifyCourseDTO;
 import nju.seec.helper.vo.CourseVO;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -25,10 +24,10 @@ public interface CourseService {
      * 修改课程
      *
      * @param user
-     * @param modifyCourseDTO
+     * @param courseDTO
      * @return
      */
-    CourseVO modifyCourse(LoginUser user, ModifyCourseDTO modifyCourseDTO);
+    CourseVO modifyCourse(LoginUser user, CourseDTO courseDTO);
 
     /**
      * 删除课程(伪删除)
@@ -36,7 +35,7 @@ public interface CourseService {
      * @param user
      * @param courseId
      */
-    void removeCourse(LoginUser user, Integer courseId);
+    void removeCourse(LoginUser user, Long courseId);
 
     /**
      * 学生选课
@@ -52,7 +51,7 @@ public interface CourseService {
      * @param user
      * @param courseId
      */
-    void quitCourse(LoginUser user, Integer courseId);
+    void quitCourse(LoginUser user, Long courseId);
 
     /**
      * 获取课程
@@ -82,4 +81,21 @@ public interface CourseService {
      * @return
      */
     Page<CourseVO> studentGetCourses(LoginUser student, String key, Pageable pageable);
+
+    /**
+     * 获取课程选课码
+     *
+     * @param user
+     * @param courseId
+     * @return
+     */
+    String getCourseCode(LoginUser user, Long courseId);
+
+    /**
+     * 取得某课程
+     *
+     * @param courseId
+     * @return
+     */
+    CourseVO getOneCourse(Long courseId);
 }

+ 61 - 0
src/main/java/nju/seec/helper/service/MessageService.java

@@ -0,0 +1,61 @@
+package nju.seec.helper.service;
+
+import nju.seec.helper.dto.LoginUser;
+import nju.seec.helper.dto.MessageDTO;
+import nju.seec.helper.entity.Message;
+import nju.seec.helper.util.enums.MessageType;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+import java.util.Set;
+
+/**
+ * @author cst
+ */
+public interface MessageService {
+    /**
+     * 创建消息
+     *
+     * @param toUserIds
+     * @param type
+     * @param content
+     * @param slideId
+     */
+    void createMessage(Set<Long> toUserIds, MessageType type, String content, Long slideId);
+
+    /**
+     * 删除消息
+     *
+     * @param user
+     * @param messageDTO
+     */
+    void deleteMessages(LoginUser user, MessageDTO messageDTO);
+
+    /**
+     * 设置消息已阅
+     *
+     * @param user
+     * @param messageDTO
+     */
+    void readMessages(LoginUser user, MessageDTO messageDTO);
+
+    /**
+     * 取得未读消息
+     *
+     * @param user
+     * @param key
+     * @param pageable
+     * @return
+     */
+    Page<Message> getUnreadMessages(LoginUser user, String key, Pageable pageable);
+
+    /**
+     * 取得已读消息
+     *
+     * @param user
+     * @param key
+     * @param pageable
+     * @return
+     */
+    Page<Message> getReadMessages(LoginUser user, String key, Pageable pageable);
+}

+ 49 - 0
src/main/java/nju/seec/helper/service/NoticeService.java

@@ -0,0 +1,49 @@
+package nju.seec.helper.service;
+
+import nju.seec.helper.dto.LoginUser;
+import nju.seec.helper.dto.NoticeDTO;
+import nju.seec.helper.vo.NoticeVO;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+/**
+ * @author cst
+ */
+public interface NoticeService {
+    /**
+     * 创建公告
+     *
+     * @param user
+     * @param noticeDTO
+     * @return
+     */
+    NoticeVO create(LoginUser user, NoticeDTO noticeDTO);
+
+    /**
+     * 修改公告
+     *
+     * @param user
+     * @param noticeDTO
+     * @return
+     */
+    NoticeVO modify(LoginUser user, NoticeDTO noticeDTO);
+
+    /**
+     * 取得公告
+     *
+     * @param user
+     * @param courseId
+     * @param key
+     * @param pageable
+     * @return
+     */
+    Page<NoticeVO> getNoticesByCourse(LoginUser user, Long courseId, String key, Pageable pageable);
+
+    /**
+     * 删除告示
+     *
+     * @param user
+     * @param noticeId
+     */
+    void deleteNotice(LoginUser user, Long noticeId);
+}

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

@@ -23,5 +23,5 @@ public interface ReplyService {
      * @param user
      * @param replyId
      */
-    void removeReply(LoginUser user, Integer replyId);
+    void removeReply(LoginUser user, Long replyId);
 }

+ 32 - 15
src/main/java/nju/seec/helper/service/SlideService.java

@@ -1,8 +1,8 @@
 package nju.seec.helper.service;
 
 import nju.seec.helper.dto.LoginUser;
-import nju.seec.helper.dto.ModifySlideDTO;
 import nju.seec.helper.dto.SlideDTO;
+import nju.seec.helper.dto.SlideFileDTO;
 import nju.seec.helper.dto.SlideStateDTO;
 import nju.seec.helper.vo.SlideVO;
 import org.springframework.data.domain.Page;
@@ -13,7 +13,7 @@ import org.springframework.data.domain.Pageable;
  */
 public interface SlideService {
     /**
-     * 创建幻灯片
+     * 创建课件
      *
      * @param teacher
      * @param slideDTO
@@ -22,16 +22,16 @@ public interface SlideService {
     SlideVO createSlide(LoginUser teacher, SlideDTO slideDTO);
 
     /**
-     * 修改幻灯片
+     * 修改课件
      *
      * @param user
-     * @param modifySlideDTO
+     * @param slideDTO
      * @return
      */
-    SlideVO modifySlide(LoginUser user, ModifySlideDTO modifySlideDTO);
+    SlideVO modifySlide(LoginUser user, SlideDTO slideDTO);
 
     /**
-     * 修改幻灯片状态
+     * 修改课件状态
      *
      * @param user
      * @param slideStateDTO
@@ -39,15 +39,23 @@ public interface SlideService {
     void modifySlideState(LoginUser user, SlideStateDTO slideStateDTO);
 
     /**
-     * 删除幻灯片(伪删除)
+     * 修改课件文件
+     *
+     * @param user
+     * @param slideFileDTO
+     */
+    void modifySlideFile(LoginUser user, SlideFileDTO slideFileDTO);
+
+    /**
+     * 删除课件(伪删除)
      *
      * @param user
      * @param slideId
      */
-    void removeSlide(LoginUser user, Integer slideId);
+    void removeSlide(LoginUser user, Long slideId);
 
     /**
-     * 教师获取幻灯片
+     * 教师获取课件
      *
      * @param teacher
      * @param key
@@ -57,7 +65,7 @@ public interface SlideService {
     Page<SlideVO> teacherGetSlides(LoginUser teacher, String key, Pageable pageable);
 
     /**
-     * 教师基于课程获取幻灯片
+     * 教师基于课程获取课件
      *
      * @param teacher
      * @param courseId
@@ -65,10 +73,10 @@ public interface SlideService {
      * @param pageable
      * @return
      */
-    Page<SlideVO> teacherGetSlidesByCourse(LoginUser teacher, Integer courseId, String key, Pageable pageable);
+    Page<SlideVO> teacherGetSlidesByCourse(LoginUser teacher, Long courseId, String key, Pageable pageable);
 
     /**
-     * 教师获取幻灯片
+     * 教师获取课件
      *
      * @param student
      * @param courseId
@@ -76,14 +84,23 @@ public interface SlideService {
      * @param pageable
      * @return
      */
-    Page<SlideVO> studentGetSlidesByCourse(LoginUser student, Integer courseId, String key, Pageable pageable);
+    Page<SlideVO> studentGetSlidesByCourse(LoginUser student, Long courseId, String key, Pageable pageable);
+
+    /**
+     * 获取课件链接
+     *
+     * @param user
+     * @param slideId
+     * @return
+     */
+    String getSlideUrl(LoginUser user, Long slideId);
 
     /**
-     * 获取幻灯片链接
+     * 获取某课件
      *
      * @param user
      * @param slideId
      * @return
      */
-    String getSlideUrl(LoginUser user, Integer slideId);
+    SlideVO getOneSlide(LoginUser user, Long slideId);
 }

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

@@ -1,9 +1,8 @@
 package nju.seec.helper.service;
 
 import nju.seec.helper.dto.LoginUser;
-import nju.seec.helper.dto.ModifyUserDTO;
-import nju.seec.helper.dto.RegisterDTO;
 import nju.seec.helper.dto.ResetPasswordDTO;
+import nju.seec.helper.dto.UserDTO;
 import nju.seec.helper.vo.UserVO;
 
 /**
@@ -13,19 +12,19 @@ public interface UserService {
     /**
      * 创建用户
      *
-     * @param registerDTO
+     * @param userDTO
      * @return
      */
-    UserVO createUser(RegisterDTO registerDTO);
+    UserVO createUser(UserDTO userDTO);
 
     /**
      * 修改用户信息
      *
      * @param loginUser
-     * @param modifyUserDTO
+     * @param userDTO
      * @return
      */
-    UserVO modifyUser(LoginUser loginUser, ModifyUserDTO modifyUserDTO);
+    UserVO modifyUser(LoginUser loginUser, UserDTO userDTO);
 
     /**
      * 重设密码
@@ -49,5 +48,5 @@ public interface UserService {
      * @param userId
      * @return
      */
-    UserVO getUserById(Integer userId);
+    UserVO getUserById(Long userId);
 }

+ 31 - 10
src/main/java/nju/seec/helper/service/impl/CommentServiceImpl.java

@@ -5,14 +5,19 @@ import nju.seec.helper.dao.SlideDAO;
 import nju.seec.helper.dto.CommentDTO;
 import nju.seec.helper.dto.LoginUser;
 import nju.seec.helper.entity.Comment;
-import nju.seec.helper.service.AuthUtil;
+import nju.seec.helper.entity.Slide;
 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.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Collections;
+
 /**
  * @author cst
  */
@@ -21,9 +26,12 @@ public class CommentServiceImpl implements CommentService {
     private final CommentDAO commentDAO;
     private final SlideDAO slideDAO;
 
-    public CommentServiceImpl(CommentDAO commentDAO, SlideDAO slideDAO) {
+    private final MessageService messageService;
+
+    public CommentServiceImpl(CommentDAO commentDAO, SlideDAO slideDAO, MessageService messageService) {
         this.commentDAO = commentDAO;
         this.slideDAO = slideDAO;
+        this.messageService = messageService;
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -38,15 +46,22 @@ public class CommentServiceImpl implements CommentService {
                 .setTitle(commentDTO.getTitle())
                 .setContent(commentDTO.getContent());
         comment = commentDAO.save(comment);
+
+        // 增加通知
+        Slide slide = slideDAO.findSlideById(comment.getSlideId());
+        if (!user.getId().equals(slide.getTeacherId())) {
+            messageService.createMessage(Collections.singleton(slide.getTeacherId()), MessageType.COMMENT_NEW, String.format("课程 [%s] 的课件 [%s] 在第%d页有新评论", slide.getCourseName(), slide.getName(), comment.getPageNumber()), slide.getId());
+        }
+
         return new CommentVO(comment);
     }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public void removeComment(LoginUser user, Integer commentId) {
+    public void removeComment(LoginUser user, Long commentId) {
         Comment comment = commentDAO.findCommentById(commentId);
 
-        AuthUtil.checkDataAuth(user.getId(), comment.getUserId(), "您无权删除该评论");
+        AuthUtils.checkDataAuth(user.getId(), comment.getUserId(), "您无权删除该评论");
 
         unTopComment(comment);
         commentDAO.delete(comment);
@@ -54,10 +69,10 @@ public class CommentServiceImpl implements CommentService {
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public void topComment(LoginUser user, Integer commentId) {
+    public void topComment(LoginUser user, Long commentId) {
         Comment comment = commentDAO.findCommentById(commentId);
 
-        AuthUtil.checkDataAuth(user.getId(), slideDAO.findSlideById(comment.getSlideId()).getTeacherId(), "您无权在该评论区置顶评论");
+        AuthUtils.checkDataAuth(user.getId(), slideDAO.findSlideById(comment.getSlideId()).getTeacherId(), "您无权在该评论区置顶评论");
 
         unTopComment(comment);
         comment.setTopNumber(commentDAO.countTopComments(comment.getSlideId(), comment.getPageNumber()) + 1);
@@ -66,10 +81,10 @@ public class CommentServiceImpl implements CommentService {
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public void unTopComment(LoginUser user, Integer commentId) {
+    public void unTopComment(LoginUser user, Long commentId) {
         Comment comment = commentDAO.findCommentById(commentId);
 
-        AuthUtil.checkDataAuth(user.getId(), slideDAO.findSlideById(comment.getSlideId()).getTeacherId(), "您无权在该评论区取消置顶评论");
+        AuthUtils.checkDataAuth(user.getId(), slideDAO.findSlideById(comment.getSlideId()).getTeacherId(), "您无权在该评论区取消置顶评论");
 
         unTopComment(comment);
         commentDAO.save(comment);
@@ -85,7 +100,13 @@ public class CommentServiceImpl implements CommentService {
 
     @Transactional(readOnly = true)
     @Override
-    public Page<CommentVO> getCommentsBySlideIdAndPageNumber(Integer slideId, Integer pageNumber, Pageable pageable) {
-        return commentDAO.findComments(slideId, pageNumber, pageable).map(CommentVO::new);
+    public Page<CommentVO> getCommentsBySlideAndPageNumber(Long slideId, Integer pageNumber, Pageable pageable) {
+        return commentDAO.findBySlideIdAndPageNumber(slideId, pageNumber, pageable).map(CommentVO::new);
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public CommentVO getOneComment(Long commentId) {
+        return new CommentVO(commentDAO.findCommentById(commentId));
     }
 }

+ 38 - 19
src/main/java/nju/seec/helper/service/impl/CourseServiceImpl.java

@@ -5,11 +5,11 @@ import nju.seec.helper.dao.CourseDAO;
 import nju.seec.helper.dto.ChooseDTO;
 import nju.seec.helper.dto.CourseDTO;
 import nju.seec.helper.dto.LoginUser;
-import nju.seec.helper.dto.ModifyCourseDTO;
 import nju.seec.helper.entity.Choose;
 import nju.seec.helper.entity.Course;
-import nju.seec.helper.service.AuthUtil;
 import nju.seec.helper.service.CourseService;
+import nju.seec.helper.service.util.AuthUtils;
+import nju.seec.helper.service.util.RefreshUtils;
 import nju.seec.helper.util.EncryptUtils;
 import nju.seec.helper.util.enums.ExceptionType;
 import nju.seec.helper.util.exception.HelperException;
@@ -27,16 +27,19 @@ public class CourseServiceImpl implements CourseService {
     private final CourseDAO courseDAO;
     private final ChooseDAO chooseDAO;
 
-    public CourseServiceImpl(CourseDAO courseDAO, ChooseDAO chooseDAO) {
+    private final RefreshUtils refreshUtils;
+
+    public CourseServiceImpl(CourseDAO courseDAO, ChooseDAO chooseDAO, RefreshUtils refreshUtils) {
         this.courseDAO = courseDAO;
         this.chooseDAO = chooseDAO;
+        this.refreshUtils = refreshUtils;
     }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
     public CourseVO createCourse(LoginUser teacher, CourseDTO courseDTO) {
         // 检查
-        checkSameCourseName(teacher.getId(), courseDTO.getName(), 0);
+        checkSameCourseName(0L, teacher.getId(), courseDTO.getName());
 
         Course course = new Course()
                 .setTeacherId(teacher.getId())
@@ -50,25 +53,26 @@ public class CourseServiceImpl implements CourseService {
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public CourseVO modifyCourse(LoginUser user, ModifyCourseDTO modifyCourseDTO) {
-        Course course = courseDAO.findCourseById(modifyCourseDTO.getId());
+    public CourseVO modifyCourse(LoginUser user, CourseDTO courseDTO) {
+        Course course = courseDAO.findCourseById(courseDTO.getId());
 
         // 检查
-        AuthUtil.checkDataAuth(user.getId(), course.getTeacherId(), "您无权修改该课程");
-        checkSameCourseName(user.getId(), modifyCourseDTO.getName(), course.getId());
+        AuthUtils.checkDataAuth(user.getId(), course.getTeacherId(), "您无权修改该课程");
+        checkSameCourseName(course.getId(), user.getId(), courseDTO.getName());
 
-        course.setName(modifyCourseDTO.getName())
-                .setBio(modifyCourseDTO.getBio())
-                .setCode(EncryptUtils.encode(modifyCourseDTO.getCode()));
+        course.setName(courseDTO.getName())
+                .setBio(courseDTO.getBio())
+                .setCode(EncryptUtils.encode(courseDTO.getCode()));
         course = courseDAO.save(course);
+        refreshUtils.refreshCourse(course);
         return new CourseVO(course);
     }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public void removeCourse(LoginUser user, Integer courseId) {
+    public void removeCourse(LoginUser user, Long courseId) {
         Course course = courseDAO.findCourseById(courseId);
-        AuthUtil.checkDataAuth(user.getId(), course.getTeacherId(), "您无权删除该课程");
+        AuthUtils.checkDataAuth(user.getId(), course.getTeacherId(), "您无权删除该课程");
 
         course.setDeleteAt(System.currentTimeMillis());
         courseDAO.save(course);
@@ -77,15 +81,15 @@ public class CourseServiceImpl implements CourseService {
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void chooseCourse(LoginUser student, ChooseDTO chooseDTO) {
-        Integer studentId = student.getId();
+        Long studentId = student.getId();
         if (chooseDAO.existsByStudentIdAndCourseId(studentId, chooseDTO.getCourseId())) {
-            throw HelperException.of(ExceptionType.FORBIDDEN, "您已选课");
+            throw HelperException.of(ExceptionType.CONFLICT, "您已选课");
         }
 
         Course course = courseDAO.findCourseById(chooseDTO.getCourseId());
 
         if (!course.getCode().equals(EncryptUtils.encode(chooseDTO.getCode()))) {
-            throw HelperException.of(ExceptionType.FORBIDDEN, "选课码错误");
+            throw HelperException.of(ExceptionType.NOT_FOUND, "选课码错误");
         }
 
         Choose choose = new Choose()
@@ -96,7 +100,7 @@ public class CourseServiceImpl implements CourseService {
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public void quitCourse(LoginUser user, Integer courseId) {
+    public void quitCourse(LoginUser user, Long courseId) {
         Choose choose = chooseDAO.findByStudentIdAndCourseId(user.getId(), courseId)
                 .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "找不到选课信息"));
         chooseDAO.deleteById(choose.getId());
@@ -125,11 +129,26 @@ public class CourseServiceImpl implements CourseService {
                 .map(CourseVO::new);
     }
 
+    @Transactional(readOnly = true)
+    @Override
+    public String getCourseCode(LoginUser user, Long courseId) {
+        Course course = courseDAO.findCourseById(courseId);
+        AuthUtils.checkDataAuth(user.getId(), course.getTeacherId(), "您无权查看该课程选课码");
+
+        return EncryptUtils.decode(course.getCode());
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public CourseVO getOneCourse(Long courseId) {
+        return new CourseVO(courseDAO.findCourseById(courseId));
+    }
+
     /**
      * 检查同名课程
      */
-    private void checkSameCourseName(Integer teacherId, String name, Integer courseId) {
-        if (courseDAO.existsByTeacherIdAndName(teacherId, name, courseId)) {
+    private void checkSameCourseName(Long courseId, Long teacherId, String name) {
+        if (courseDAO.existsByTeacherIdAndName(courseId, teacherId, name)) {
             throw HelperException.of(ExceptionType.CONFLICT, "该课程名已使用");
         }
     }

+ 65 - 0
src/main/java/nju/seec/helper/service/impl/MessageServiceImpl.java

@@ -0,0 +1,65 @@
+package nju.seec.helper.service.impl;
+
+import nju.seec.helper.dao.MessageDAO;
+import nju.seec.helper.dto.LoginUser;
+import nju.seec.helper.dto.MessageDTO;
+import nju.seec.helper.entity.Message;
+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;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author cst
+ */
+@Service
+public class MessageServiceImpl implements MessageService {
+    private final MessageDAO messageDAO;
+
+    public MessageServiceImpl(MessageDAO messageDAO) {
+        this.messageDAO = messageDAO;
+    }
+
+    @Async
+    @Override
+    public void createMessage(Set<Long> toUserIds, MessageType type, String content, Long slideId) {
+        messageDAO.saveAll(
+                toUserIds.parallelStream()
+                        .map(toUserId -> new Message()
+                                .setToUserId(toUserId)
+                                .setType(type)
+                                .setContent(content)
+                                .setSlideId(slideId))
+                        .collect(Collectors.toSet()));
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void deleteMessages(LoginUser user, MessageDTO messageDTO) {
+        messageDAO.delete(user.getId(), messageDTO.getIds());
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void readMessages(LoginUser user, MessageDTO messageDTO) {
+        messageDAO.updateRead(user.getId(), messageDTO.getIds());
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public Page<Message> getUnreadMessages(LoginUser user, String key, Pageable pageable) {
+        return messageDAO.findByToUserIdAndReadAndContentContains(user.getId(), false, key, pageable);
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public Page<Message> getReadMessages(LoginUser user, String key, Pageable pageable) {
+        return messageDAO.findByToUserIdAndReadAndContentContains(user.getId(), true, key, pageable);
+    }
+}

+ 74 - 0
src/main/java/nju/seec/helper/service/impl/NoticeServiceImpl.java

@@ -0,0 +1,74 @@
+package nju.seec.helper.service.impl;
+
+import nju.seec.helper.dao.ChooseDAO;
+import nju.seec.helper.dao.CourseDAO;
+import nju.seec.helper.dao.NoticeDAO;
+import nju.seec.helper.dto.LoginUser;
+import nju.seec.helper.dto.NoticeDTO;
+import nju.seec.helper.entity.Course;
+import nju.seec.helper.entity.Notice;
+import nju.seec.helper.service.MessageService;
+import nju.seec.helper.service.NoticeService;
+import nju.seec.helper.service.util.AuthUtils;
+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.stereotype.Service;
+
+import java.util.Set;
+
+/**
+ * @author cst
+ */
+@Service
+public class NoticeServiceImpl implements NoticeService {
+    private final CourseDAO courseDAO;
+    private final ChooseDAO chooseDAO;
+    private final NoticeDAO noticeDAO;
+
+    private final MessageService messageService;
+
+    public NoticeServiceImpl(CourseDAO courseDAO, ChooseDAO chooseDAO, NoticeDAO noticeDAO, MessageService messageService) {
+        this.courseDAO = courseDAO;
+        this.chooseDAO = chooseDAO;
+        this.noticeDAO = noticeDAO;
+        this.messageService = messageService;
+    }
+
+    @Override
+    public NoticeVO create(LoginUser user, NoticeDTO noticeDTO) {
+        Course course = courseDAO.findCourseById(noticeDTO.getCourseId());
+        AuthUtils.checkDataAuth(user.getId(), course.getTeacherId(), "您无权创建该课程的公告");
+        Notice notice = new Notice()
+                .setCourseId(noticeDTO.getCourseId())
+                .setTeacherId(user.getId())
+                .setTitle(noticeDTO.getTitle())
+                .setContent(noticeDTO.getContent());
+
+        Set<Long> studentIds = chooseDAO.findStudentIdsByCourseId(noticeDTO.getCourseId());
+        messageService.createMessage(studentIds, MessageType.NOTICE_NEW, String.format("课程 [%s] 发布了标题为 [%s] 的公告", course.getName(), notice.getTitle()), null);
+        return new NoticeVO(noticeDAO.save(notice));
+    }
+
+    @Override
+    public NoticeVO modify(LoginUser user, NoticeDTO noticeDTO) {
+        Notice notice = noticeDAO.findNoticeById(noticeDTO.getId());
+        AuthUtils.checkDataAuth(user.getId(), notice.getTeacherId(), "您无权修改该公告");
+        notice.setTitle(noticeDTO.getTitle())
+                .setContent(noticeDTO.getContent());
+        return new NoticeVO(noticeDAO.save(notice));
+    }
+
+    @Override
+    public Page<NoticeVO> getNoticesByCourse(LoginUser user, Long courseId, String key, Pageable pageable) {
+        return noticeDAO.findByCourseIdAndKey(courseId, key, pageable).map(NoticeVO::new);
+    }
+
+    @Override
+    public void deleteNotice(LoginUser user, Long noticeId) {
+        Notice notice = noticeDAO.findNoticeById(noticeId);
+        AuthUtils.checkDataAuth(user.getId(), notice.getTeacherId(), "您无权删除该公告");
+        noticeDAO.deleteById(noticeId);
+    }
+}

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

@@ -8,14 +8,19 @@ import nju.seec.helper.dto.LoginUser;
 import nju.seec.helper.dto.ReplyDTO;
 import nju.seec.helper.entity.Comment;
 import nju.seec.helper.entity.Reply;
-import nju.seec.helper.service.AuthUtil;
+import nju.seec.helper.entity.Slide;
+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;
 
+import java.util.Collections;
+
 /**
  * @author cst
  */
@@ -26,20 +31,23 @@ public class ReplyServiceImpl implements ReplyService {
     private final CommentDAO commentDAO;
     private final ReplyDAO replyDAO;
 
-    public ReplyServiceImpl(SlideDAO slideDAO, CommentDAO commentDAO, ReplyDAO replyDAO) {
+    private final MessageService messageService;
+
+    public ReplyServiceImpl(SlideDAO slideDAO, CommentDAO commentDAO, ReplyDAO replyDAO, MessageService messageService) {
         this.slideDAO = slideDAO;
         this.commentDAO = commentDAO;
         this.replyDAO = replyDAO;
+        this.messageService = messageService;
     }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
     public ReplyVO createReply(LoginUser user, ReplyDTO replyDTO) {
         Comment comment = commentDAO.findCommentById(replyDTO.getCommentId());
-        AuthUtil.checkDataAuth(user.getId(), slideDAO.findSlideById(comment.getSlideId()).getTeacherId(), "您无权回复该评论");
+        AuthUtils.checkDataAuth(user.getId(), slideDAO.findSlideById(comment.getSlideId()).getTeacherId(), "您无权回复该评论");
 
         if (comment.getReply() != null) {
-            throw HelperException.of(ExceptionType.FORBIDDEN, "您已回复该评论");
+            throw HelperException.of(ExceptionType.CONFLICT, "您已回复该评论");
         }
         Reply reply = new Reply()
                 .setComment(comment)
@@ -47,14 +55,21 @@ public class ReplyServiceImpl implements ReplyService {
                 .setContent(replyDTO.getContent());
         reply.setComment(comment);
         reply = replyDAO.save(reply);
+
+        // 增加通知
+        if (!user.getId().equals(comment.getUserId())) {
+            Slide slide = slideDAO.findSlideById(comment.getSlideId());
+            messageService.createMessage(Collections.singleton(comment.getUserId()), MessageType.COMMENT_REPLY, String.format("您在课件 [%s] 第%d页发表的讨论帖已收到回复", slide.getName(), comment.getPageNumber()), slide.getId());
+        }
+
         return new ReplyVO(reply);
     }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public void removeReply(LoginUser user, Integer replyId) {
+    public void removeReply(LoginUser user, Long replyId) {
         Reply reply = replyDAO.findReplyById(replyId);
-        AuthUtil.checkDataAuth(user.getId(), reply.getTeacherId(), "您无权回复该评论");
+        AuthUtils.checkDataAuth(user.getId(), reply.getTeacherId(), "您无权回复该评论");
 
         Comment comment = reply.getComment();
         comment.setReply(null);

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

@@ -1,38 +1,38 @@
 package nju.seec.helper.service.impl;
 
 import com.google.common.collect.ImmutableSet;
-import lombok.Cleanup;
+import lombok.AllArgsConstructor;
 import lombok.SneakyThrows;
 import nju.seec.helper.dao.ChooseDAO;
 import nju.seec.helper.dao.CourseDAO;
 import nju.seec.helper.dao.SlideDAO;
 import nju.seec.helper.dto.LoginUser;
-import nju.seec.helper.dto.ModifySlideDTO;
 import nju.seec.helper.dto.SlideDTO;
+import nju.seec.helper.dto.SlideFileDTO;
 import nju.seec.helper.dto.SlideStateDTO;
 import nju.seec.helper.entity.Course;
 import nju.seec.helper.entity.Slide;
-import nju.seec.helper.service.AuthUtil;
-import nju.seec.helper.service.QuizService;
+import nju.seec.helper.service.MessageService;
 import nju.seec.helper.service.SlideService;
+import nju.seec.helper.service.util.AuthUtils;
 import nju.seec.helper.util.CacheUtils;
 import nju.seec.helper.util.Consts;
 import nju.seec.helper.util.FileUtils;
 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.vo.SlideVO;
 import org.apache.pdfbox.pdmodel.PDDocument;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 
-import java.io.InputStream;
-import java.time.LocalDateTime;
+import java.io.File;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -40,6 +40,7 @@ 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;
@@ -49,13 +50,16 @@ public class SlideServiceImpl implements SlideService {
     private final FileUtils fileUtils;
     private final CacheUtils cacheUtils;
 
-    public SlideServiceImpl(CourseDAO courseDAO, SlideDAO slideDAO, ChooseDAO chooseDAO, QuizService quizService, FileUtils fileUtils, CacheUtils cacheUtils) {
+    private final MessageService messageService;
+
+    public SlideServiceImpl(CourseDAO courseDAO, SlideDAO slideDAO, ChooseDAO chooseDAO, FileUtils fileUtils, CacheUtils cacheUtils, MessageService messageService) {
         this.courseDAO = courseDAO;
         this.slideDAO = slideDAO;
         this.chooseDAO = chooseDAO;
         this.quizService = quizService;
         this.fileUtils = fileUtils;
         this.cacheUtils = cacheUtils;
+        this.messageService = messageService;
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -63,7 +67,9 @@ public class SlideServiceImpl implements SlideService {
     @Override
     public SlideVO createSlide(LoginUser teacher, SlideDTO slideDTO) {
         Course course = courseDAO.findCourseById(slideDTO.getCourseId());
-        AuthUtil.checkDataAuth(teacher.getId(), course.getTeacherId(), "您无权创建该课程的幻灯片");
+        AuthUtils.checkDataAuth(teacher.getId(), course.getTeacherId(), "您无权创建该课程的课件");
+
+        checkSameName(0L, slideDTO.getCourseId(), slideDTO.getName());
 
         Slide slide = new Slide()
                 .setCourseId(slideDTO.getCourseId())
@@ -71,39 +77,42 @@ public class SlideServiceImpl implements SlideService {
                 .setTeacherId(teacher.getId())
                 .setTeacherName(teacher.getName())
                 .setName(slideDTO.getName())
-                .setState(SlideState.DRAFT);
+                .setState(slideDTO.getState());
 
-        // 解析文件页数
         MultipartFile file = slideDTO.getFile();
-        int pages;
-        switch (Objects.requireNonNull(file.getContentType())) {
-            // pdf
-            case "application/pdf":
-                @Cleanup InputStream fileInputStream = file.getInputStream();
-                PDDocument doc = PDDocument.load(fileInputStream);
-                pages = doc.getNumberOfPages();
-                break;
-            default:
-                throw HelperException.of(ExceptionType.FORBIDDEN, "不支持的文件类型");
-        }
-
-        slide.setPages(pages);
+        FileInfo fileInfo = getFileInfo(file);
+        slide.setPages(fileInfo.pages);
+        slide.setObjectName("temp");
+        slide = slideDAO.save(slide);
 
-        // 上传文件
-        String objectName = fileUtils.upload(LocalDateTime.now().toString() + "-" + file.getOriginalFilename(), file.getInputStream());
+        String objectName = getObjectName(slide.getId(), slide.getName(), fileInfo.suffix);
         slide.setObjectName(objectName);
+        slide = slideDAO.save(slide);
 
-        return new SlideVO(slideDAO.save(slide));
+        fileUtils.upload(objectName, file.getInputStream());
+
+        return new SlideVO(slide);
     }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public SlideVO modifySlide(LoginUser user, ModifySlideDTO modifySlideDTO) {
-        Slide slide = slideDAO.findSlideById(modifySlideDTO.getId());
-        AuthUtil.checkDataAuth(user.getId(), slide.getTeacherId(), "您无权修改该幻灯片");
+    public SlideVO modifySlide(LoginUser user, SlideDTO slideDTO) {
+        Slide slide = slideDAO.findSlideById(slideDTO.getId());
+        AuthUtils.checkDataAuth(user.getId(), slide.getTeacherId(), "您无权修改该课件");
+
+        checkSameName(slide.getId(), slide.getCourseId(), slideDTO.getName());
 
-        slide.setName(modifySlideDTO.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);
+        }
+
         return new SlideVO(slide);
     }
 
@@ -111,22 +120,73 @@ public class SlideServiceImpl implements SlideService {
     @Override
     public void modifySlideState(LoginUser user, SlideStateDTO slideStateDTO) {
         Slide slide = slideDAO.findSlideById(slideStateDTO.getId());
-        AuthUtil.checkDataAuth(user.getId(), slide.getTeacherId(), "您无权修改该幻灯片状态");
+        AuthUtils.checkDataAuth(user.getId(), slide.getTeacherId(), "您无权修改该课件状态");
 
         slide.setState(slideStateDTO.getState());
+        slide = slideDAO.save(slide);
+
+        // 增加通知
+        if (slide.getState() == SlideState.IN_CLASS) {
+            Set<Long> studentIds = chooseDAO.findStudentIdsByCourseId(slide.getCourseId());
+            messageService.createMessage(studentIds, MessageType.SLIDE_IN_CLASS, String.format("课程 [%s] 的课件 [%s] 已开课", slide.getCourseName(), slide.getName()), slide.getId());
+        }
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @SneakyThrows
+    @Override
+    public void modifySlideFile(LoginUser user, SlideFileDTO slideFileDTO) {
+        Slide slide = slideDAO.findSlideById(slideFileDTO.getId());
+        AuthUtils.checkDataAuth(user.getId(), slide.getTeacherId(), "您无权修改该课件文件");
+
+        MultipartFile file = slideFileDTO.getFile();
+        FileInfo fileInfo = getFileInfo(file);
+        slide.setPages(fileInfo.pages);
+
+        String oldObjectName = slide.getObjectName();
+        String newObjectName = getObjectName(slide.getId(), slide.getName(), fileInfo.suffix);
+        slide.setObjectName(newObjectName);
         slideDAO.save(slide);
-        quizService.modifySlideState(slide);
+
+        fileUtils.upload(newObjectName, file.getInputStream());
+        if (!oldObjectName.equals(newObjectName)) {
+            fileUtils.delete(oldObjectName);
+        }
     }
 
+    @Transactional(rollbackFor = Exception.class)
     @Override
-    public void removeSlide(LoginUser user, Integer slideId) {
+    public void removeSlide(LoginUser user, Long slideId) {
         Slide slide = slideDAO.findSlideById(slideId);
-        AuthUtil.checkDataAuth(user.getId(), slide.getTeacherId(), "您无权删除该幻灯片");
+        AuthUtils.checkDataAuth(user.getId(), slide.getTeacherId(), "您无权删除该课件");
 
         slide.setDeleteAt(System.currentTimeMillis());
         slideDAO.save(slide);
     }
 
+    @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, "不支持的文件类型");
+        }
+    }
+
+    @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;
+    }
+
     @Transactional(readOnly = true)
     @Override
     public Page<SlideVO> teacherGetSlides(LoginUser teacher, String key, Pageable pageable) {
@@ -135,15 +195,15 @@ public class SlideServiceImpl implements SlideService {
 
     @Transactional(readOnly = true)
     @Override
-    public Page<SlideVO> teacherGetSlidesByCourse(LoginUser teacher, Integer courseId, String key, Pageable pageable) {
+    public Page<SlideVO> teacherGetSlidesByCourse(LoginUser teacher, Long courseId, String key, Pageable pageable) {
         return slideDAO.findByCourseIdAndTeacherIdAndNameContains(courseId, teacher.getId(), key, pageable).map(SlideVO::new);
     }
 
     @Transactional(readOnly = true)
     @Override
-    public Page<SlideVO> studentGetSlidesByCourse(LoginUser student, Integer courseId, String key, Pageable pageable) {
+    public Page<SlideVO> studentGetSlidesByCourse(LoginUser student, Long courseId, String key, Pageable pageable) {
         if (!chooseDAO.existsByStudentIdAndCourseId(student.getId(), courseId)) {
-            throw HelperException.of(ExceptionType.FORBIDDEN, "你无权查看该课程下的幻灯片");
+            throw HelperException.of(ExceptionType.FORBIDDEN, "你无权查看该课程下的课件");
         }
 
         return slideDAO.findByCourseIdAndStateNotInAndNameContains(courseId, ImmutableSet.of(SlideState.DRAFT), key, pageable).map(SlideVO::new);
@@ -151,8 +211,7 @@ public class SlideServiceImpl implements SlideService {
 
     @Transactional(readOnly = true)
     @Override
-    public String getSlideUrl(LoginUser user, Integer slideId) {
-
+    public String getSlideUrl(LoginUser user, Long slideId) {
         Slide slide = slideDAO.findSlideById(slideId);
         checkDataAccessAuth(user, slide);
 
@@ -165,8 +224,25 @@ public class SlideServiceImpl implements SlideService {
         return url;
     }
 
+    @Transactional(readOnly = true)
+    @Override
+    public SlideVO getOneSlide(LoginUser user, Long slideId) {
+        Slide slide = slideDAO.findSlideById(slideId);
+        checkDataAccessAuth(user, slide);
+        return new SlideVO(slide);
+    }
+
+    /**
+     * 检查同名课件
+     */
+    private void checkSameName(Long slideId, Long courseId, String name) {
+        if (slideDAO.existsByCourseIdAndName(slideId, courseId, name)) {
+            throw HelperException.of(ExceptionType.CONFLICT, "该课件名已使用");
+        }
+    }
+
     private void checkDataAccessAuth(LoginUser user, Slide slide) {
-        HelperException forbiddenEx = HelperException.of(ExceptionType.FORBIDDEN, "您无权访问该幻灯片");
+        HelperException forbiddenEx = HelperException.of(ExceptionType.FORBIDDEN, "您无权访问该课件");
 
         switch (user.getType()) {
             case TEACHER:
@@ -175,7 +251,8 @@ public class SlideServiceImpl implements SlideService {
                 }
                 break;
             case STUDENT:
-                if (!chooseDAO.existsByStudentIdAndCourseId(user.getId(), slide.getCourseId())) {
+                if (!chooseDAO.existsByStudentIdAndCourseId(user.getId(), slide.getCourseId())
+                        || slide.getState() == SlideState.DRAFT) {
                     throw forbiddenEx;
                 }
                 break;

+ 27 - 24
src/main/java/nju/seec/helper/service/impl/UserServiceImpl.java

@@ -3,12 +3,12 @@ package nju.seec.helper.service.impl;
 import com.google.common.collect.ImmutableMap;
 import nju.seec.helper.dao.UserDAO;
 import nju.seec.helper.dto.LoginUser;
-import nju.seec.helper.dto.ModifyUserDTO;
-import nju.seec.helper.dto.RegisterDTO;
 import nju.seec.helper.dto.ResetPasswordDTO;
+import nju.seec.helper.dto.UserDTO;
 import nju.seec.helper.entity.User;
-import nju.seec.helper.service.AuthUtil;
 import nju.seec.helper.service.UserService;
+import nju.seec.helper.service.util.AuthUtils;
+import nju.seec.helper.service.util.RefreshUtils;
 import nju.seec.helper.util.CacheUtils;
 import nju.seec.helper.util.Consts;
 import nju.seec.helper.util.EncryptUtils;
@@ -27,6 +27,7 @@ import java.util.Map;
 @Service
 public class UserServiceImpl implements UserService {
     private final CacheUtils cacheUtils;
+    private final RefreshUtils refreshUtils;
     private final UserDAO userDAO;
     private final static Map<String, UserType> EMAIL_SUFFIX_AND_USER_TYPE_MAP =
             ImmutableMap.of(
@@ -34,32 +35,33 @@ public class UserServiceImpl implements UserService {
                     "nju.edu.cn", UserType.TEACHER
             );
 
-    public UserServiceImpl(UserDAO userDAO, CacheUtils cacheUtils) {
+    public UserServiceImpl(UserDAO userDAO, CacheUtils cacheUtils, RefreshUtils refreshUtils) {
         this.userDAO = userDAO;
         this.cacheUtils = cacheUtils;
+        this.refreshUtils = refreshUtils;
     }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public UserVO createUser(RegisterDTO registerDTO) {
-        checkSameEmail(registerDTO.getEmail());
-        checkSamePhone(registerDTO.getPhone());
+    public UserVO createUser(UserDTO userDTO) {
+        checkSameEmail(userDTO.getEmail());
+        checkSamePhone(userDTO.getPhone());
 
-        String email = registerDTO.getEmail();
-        if (!registerDTO.getEmailCode().equals(cacheUtils.get(Consts.EMAIL_CACHE_NAME, email))) {
-            throw HelperException.of(ExceptionType.FORBIDDEN, "邮箱或验证码错误");
+        String email = userDTO.getEmail();
+        if (!userDTO.getEmailCode().equals(cacheUtils.get(Consts.EMAIL_CACHE_NAME, email))) {
+            throw HelperException.of(ExceptionType.NOT_FOUND, "邮箱或验证码错误");
         }
 
-        String phone = registerDTO.getPhone();
-        if (!registerDTO.getPhoneCode().equals(cacheUtils.get(Consts.PHONE_CACHE_NAME, phone))) {
-            throw HelperException.of(ExceptionType.FORBIDDEN, "手机号或验证码错误");
+        String phone = userDTO.getPhone();
+        if (!userDTO.getPhoneCode().equals(cacheUtils.get(Consts.PHONE_CACHE_NAME, phone))) {
+            throw HelperException.of(ExceptionType.NOT_FOUND, "手机号或验证码错误");
         }
 
         User user = new User()
-                .setName(registerDTO.getName())
-                .setEmail(registerDTO.getEmail())
-                .setPhone(registerDTO.getPhone())
-                .setPassword(EncryptUtils.encode(registerDTO.getPassword()))
+                .setName(userDTO.getName())
+                .setEmail(userDTO.getEmail())
+                .setPhone(userDTO.getPhone())
+                .setPassword(EncryptUtils.encode(userDTO.getPassword()))
                 .setType(getUserType(email));
 
         user = userDAO.save(user);
@@ -74,11 +76,12 @@ public class UserServiceImpl implements UserService {
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public UserVO modifyUser(LoginUser loginUser, ModifyUserDTO modifyUserDTO) {
+    public UserVO modifyUser(LoginUser loginUser, UserDTO userDTO) {
         User user = userDAO.findUserById(loginUser.getId());
-        AuthUtil.checkDataAuth(loginUser.getId(), user.getId(), "您无权修改该用户的个人信息");
-        user.setName(modifyUserDTO.getName());
+        AuthUtils.checkDataAuth(loginUser.getId(), user.getId(), "您无权修改该用户的个人信息");
+        user.setName(userDTO.getName());
         user = userDAO.save(user);
+        refreshUtils.refreshUser(user);
         return new UserVO(user);
     }
 
@@ -86,10 +89,10 @@ public class UserServiceImpl implements UserService {
     @Override
     public void resetPasswordByEmail(ResetPasswordDTO resetPasswordDTO) {
         User user = userDAO.findByEmail(resetPasswordDTO.getUsername())
-                .orElseThrow(() -> HelperException.of(ExceptionType.FORBIDDEN, "该邮箱未在系统中注册"));
+                .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "该邮箱未在系统中注册"));
 
         if (!resetPasswordDTO.getCode().equals(cacheUtils.get(Consts.EMAIL_CACHE_NAME, resetPasswordDTO.getUsername()))) {
-            throw HelperException.of(ExceptionType.FORBIDDEN, "邮箱或验证码错误");
+            throw HelperException.of(ExceptionType.NOT_FOUND, "邮箱或验证码错误");
         }
 
         user.setPassword(EncryptUtils.encode(resetPasswordDTO.getPassword()));
@@ -103,12 +106,12 @@ public class UserServiceImpl implements UserService {
         return userDAO
                 .findByEmailAndPassword(email, EncryptUtils.encode(password))
                 .map(UserVO::new)
-                .orElseThrow(() -> HelperException.of(ExceptionType.FORBIDDEN, "邮箱或密码错误"));
+                .orElseThrow(() -> HelperException.of(ExceptionType.NOT_FOUND, "邮箱或密码错误"));
     }
 
     @Transactional(readOnly = true)
     @Override
-    public UserVO getUserById(Integer userId) {
+    public UserVO getUserById(Long userId) {
         return new UserVO(userDAO.findUserById(userId));
     }
 

+ 4 - 4
src/main/java/nju/seec/helper/service/AuthUtil.java → src/main/java/nju/seec/helper/service/util/AuthUtils.java

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

+ 34 - 0
src/main/java/nju/seec/helper/service/util/RefreshUtils.java

@@ -0,0 +1,34 @@
+package nju.seec.helper.service.util;
+
+import nju.seec.helper.dao.CommentDAO;
+import nju.seec.helper.dao.CourseDAO;
+import nju.seec.helper.dao.SlideDAO;
+import nju.seec.helper.entity.Course;
+import nju.seec.helper.entity.User;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author cst
+ */
+@Component
+public class RefreshUtils {
+    private final CourseDAO courseDAO;
+    private final SlideDAO slideDAO;
+    private final CommentDAO commentDAO;
+
+    public RefreshUtils(CourseDAO courseDAO, SlideDAO slideDAO, CommentDAO commentDAO) {
+        this.courseDAO = courseDAO;
+        this.slideDAO = slideDAO;
+        this.commentDAO = commentDAO;
+    }
+
+    public void refreshUser(User user) {
+        courseDAO.updateUser(user.getId(), user.getName());
+        slideDAO.updateUser(user.getId(), user.getName());
+        commentDAO.updateUser(user.getId(), user.getName(), user.getType());
+    }
+
+    public void refreshCourse(Course course) {
+        slideDAO.updateCourse(course.getId(), course.getName());
+    }
+}

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

@@ -6,9 +6,6 @@ package nju.seec.helper.util;
 public class Consts {
     public static final String SESSION_USER_NAME = "helper_user";
 
-    public static final String SCHOOL_EMAIL_REGEX = ".+@(nju.edu.cn|smail.nju.edu.cn)";
-    public static final String PHONE_REGEX = "1[3|4|5|7|8]\\d{9}";
-
     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";

+ 64 - 9
src/main/java/nju/seec/helper/util/FileUtils.java

@@ -1,9 +1,15 @@
 package nju.seec.helper.util;
 
+import com.aliyun.oss.ClientException;
 import com.aliyun.oss.OSS;
 import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.OSSException;
 import com.aliyun.oss.model.PutObjectRequest;
+import lombok.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 org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 
 import java.io.InputStream;
@@ -15,6 +21,7 @@ import java.util.Date;
  *
  * @author cst
  */
+@Slf4j
 @Component
 public class FileUtils {
     private final OssProperties ossProperties;
@@ -23,19 +30,67 @@ public class FileUtils {
         this.ossProperties = ossProperties;
     }
 
-    public String upload(String objectName, InputStream inputStream) {
-        OSS ossClient = new OSSClientBuilder().build(ossProperties.getEndpoint(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret());
+    public void upload(String objectName, InputStream inputStream) {
+        OSS ossClient = getOss();
         PutObjectRequest putObjectRequest = new PutObjectRequest(ossProperties.getBucketName(), objectName, inputStream);
-        ossClient.putObject(putObjectRequest);
-        ossClient.shutdown();
-        return objectName;
+        try {
+            ossClient.putObject(putObjectRequest);
+        } catch (OSSException | ClientException e) {
+            log.error(e.getMessage());
+            throw HelperException.of(ExceptionType.ERROR, "文件上传失败");
+        } finally {
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
     }
 
     public String getUrl(String objectName, long seconds) {
-        OSS ossClient = new OSSClientBuilder().build(ossProperties.getEndpoint(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret());
+        OSS ossClient = getOss();
         Date expiration = new Date(System.currentTimeMillis() + seconds * 1000);
-        URL url = ossClient.generatePresignedUrl(ossProperties.getBucketName(), objectName, expiration);
-        ossClient.shutdown();
-        return url.toString();
+        try {
+            URL url = ossClient.generatePresignedUrl(ossProperties.getBucketName(), objectName, expiration);
+            return url.toString();
+        } catch (OSSException | ClientException e) {
+            log.error(e.getMessage());
+            throw HelperException.of(ExceptionType.ERROR, "获取文件链接失败");
+        } finally {
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+    }
+
+    public void copy(String sourceObjectName, String destinationObjectName) {
+        OSS ossClient = getOss();
+        try {
+            ossClient.copyObject(ossProperties.getBucketName(), sourceObjectName, ossProperties.getBucketName(), destinationObjectName);
+        } catch (OSSException | ClientException e) {
+            log.error(e.getMessage());
+            throw HelperException.of(ExceptionType.ERROR, "文件复制失败");
+        } finally {
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+    }
+
+    @Async
+    public void delete(String objectName) {
+        OSS ossClient = getOss();
+        try {
+            ossClient.deleteObject(ossProperties.getBucketName(), objectName);
+        } catch (OSSException | ClientException e) {
+            log.error(e.getMessage());
+            throw HelperException.of(ExceptionType.ERROR, "文件删除失败");
+        } finally {
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+    }
+
+    private OSS getOss() {
+        return new OSSClientBuilder().build(ossProperties.getEndpoint(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret());
     }
 }

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

@@ -1,24 +1,24 @@
 package nju.seec.helper.util;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
 import lombok.experimental.UtilityClass;
-import org.springframework.stereotype.Component;
+import nju.seec.helper.util.serializer.LocalDateTimeSerializer;
+
+import java.time.LocalDateTime;
 
 /**
  * @author cst
  */
 @UtilityClass
 public class JsonUtils {
+    private static final Gson GSON = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer()).create();
+
     public String toJson(Object o) {
-        return new Gson().toJson(o);
-    }
-    private ObjectMapper objectMapper = new ObjectMapper();
-    public <T> T fromJson(String json, Class<T> classOfT) {
-        return new Gson().fromJson(json, classOfT);
+        return GSON.toJson(o);
     }
 
-    public ObjectMapper getJsonMapper() {
-        return objectMapper;
+    public <T> T fromJson(String json, Class<T> classOfT) {
+        return GSON.fromJson(json, classOfT);
     }
 }

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

@@ -0,0 +1,23 @@
+package nju.seec.helper.util.enums;
+
+/**
+ * @author cst
+ */
+public enum MessageType {
+    /**
+     * 课件开课
+     */
+    SLIDE_IN_CLASS,
+    /**
+     * 新的评论
+     */
+    COMMENT_NEW,
+    /**
+     * 评论被老师回复
+     */
+    COMMENT_REPLY,
+    /**
+     * 新公告
+     */
+    NOTICE_NEW
+}

+ 21 - 0
src/main/java/nju/seec/helper/util/serializer/LocalDateTimeSerializer.java

@@ -0,0 +1,21 @@
+package nju.seec.helper.util.serializer;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+import java.lang.reflect.Type;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * @author cst
+ */
+public class LocalDateTimeSerializer implements JsonSerializer<LocalDateTime> {
+
+    @Override
+    public JsonElement serialize(LocalDateTime localDateTime, Type type, JsonSerializationContext jsonSerializationContext) {
+        return new JsonPrimitive(localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
+    }
+}

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

@@ -12,11 +12,11 @@ import java.time.LocalDateTime;
  */
 @Data
 public class CommentVO {
-    private Integer id;
+    private Long id;
     /**
-     * 幻灯片ID
+     * 课件ID
      */
-    private Integer slideId;
+    private Long slideId;
     /**
      * 所属页码
      */
@@ -27,7 +27,7 @@ public class CommentVO {
     private Integer topNumber;
     private String title;
     private String content;
-    private Integer userId;
+    private Long userId;
     private String userName;
     private UserType userType;
     private LocalDateTime createAt;

+ 2 - 2
src/main/java/nju/seec/helper/vo/CourseVO.java

@@ -11,12 +11,12 @@ import java.time.LocalDateTime;
  */
 @Data
 public class CourseVO {
-    private Integer id;
+    private Long id;
     private String name;
     private String bio;
     private LocalDateTime createAt;
 
-    private Integer teacherId;
+    private Long teacherId;
     private String teacherName;
 
     public CourseVO(@NonNull Course course) {

+ 28 - 0
src/main/java/nju/seec/helper/vo/NoticeVO.java

@@ -0,0 +1,28 @@
+package nju.seec.helper.vo;
+
+import lombok.Data;
+import nju.seec.helper.entity.Notice;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author cst
+ */
+@Data
+public class NoticeVO {
+    private Long id;
+    private Long courseId;
+    private String title;
+    private String content;
+    private LocalDateTime createAt;
+    private LocalDateTime updateAt;
+
+    public NoticeVO(Notice notice) {
+        this.id = notice.getId();
+        this.courseId = notice.getCourseId();
+        this.title = notice.getTitle();
+        this.content = notice.getContent();
+        this.createAt = notice.getCreateAt();
+        this.updateAt = notice.getUpdateAt();
+    }
+}

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

@@ -11,7 +11,7 @@ import java.time.LocalDateTime;
  */
 @Data
 public class ReplyVO {
-    private Integer id;
+    private Long id;
     private String content;
     private LocalDateTime createAt;
 

+ 3 - 3
src/main/java/nju/seec/helper/vo/SlideVO.java

@@ -12,10 +12,10 @@ import java.time.LocalDateTime;
  */
 @Data
 public class SlideVO {
-    private Integer id;
-    private Integer courseId;
+    private Long id;
+    private Long courseId;
     private String courseName;
-    private Integer teacherId;
+    private Long teacherId;
     private String teacherName;
     private SlideState state;
     private String name;

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

@@ -12,7 +12,7 @@ import java.time.LocalDateTime;
  */
 @Data
 public class UserVO {
-    private Integer id;
+    private Long id;
     private String name;
     private String email;
     private String phone;

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

@@ -0,0 +1,58 @@
+spring:
+  datasource:
+    url: jdbc:mysql://localhost:13306/helper?setUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: NJU67helper
+  jpa:
+    open-in-view: true
+    hibernate:
+      ddl-auto: update
+    database: mysql
+    database-platform: org.hibernate.dialect.MySQL8Dialect
+    show-sql: false
+  http:
+    encoding:
+      force: true
+  redis:
+    host: localhost
+    port: 6379
+  data:
+    redis:
+      repositories:
+        enabled: false
+  mail:
+    host: smtp.exmail.qq.com
+    username: noreply@seecoder.cn
+    password: eNrZcqucgcFtLC29
+    properties:
+      mail:
+        smtp:
+          auth: true
+          socketFactory:
+            class: javax.net.ssl.SSLSocketFactory
+            port: 465
+          starttls:
+            enable: true
+            required: true
+  servlet:
+    multipart:
+      max-file-size: 50MB
+      max-request-size: 100MB
+aliyun:
+  oss:
+    endpoint: oss-cn-hangzhou.aliyuncs.com
+    accessKeyId: LTAI4Fi1qmL4iuhH8t7G7r2H
+    accessKeySecret: 7tJWyQqa0cMkQGaPMMvtH6c0VLiOO2
+    bucketName: seec-helper-pdf
+  sms:
+    accessKeyId: LTAI4FiAXXyNfYJLweBXpKPx
+    accessSecret: IBoC7KGkZHOvxZQIfQgp98ZY48AVjW
+    signName: seeccoder
+    templateCode: SMS_181555598
+
+# 自定义数据
+helper:
+  mail:
+    from: ${spring.mail.username}
+    subject: seecoder

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

@@ -4,13 +4,17 @@ spring:
     driver-class-name: com.mysql.cj.jdbc.Driver
     username: root
     password: 123456
+    schema: classpath:update.sql
+    schema-username: root
+    schema-password: 123456
+    initialization-mode: always
   jpa:
     open-in-view: true
     hibernate:
       ddl-auto: update
     database: mysql
     database-platform: org.hibernate.dialect.MySQL8Dialect
-#    show-sql: true
+    show-sql: true
   http:
     encoding:
       force: true
@@ -22,9 +26,9 @@ spring:
       repositories:
         enabled: false
   mail:
-    host: smtp.163.com
-    username: seecoder@163.com
-    password: seec67NJU
+    host: smtp.exmail.qq.com
+    username: noreply@seecoder.cn
+    password: eNrZcqucgcFtLC29
     properties:
       mail:
         smtp:
@@ -44,7 +48,7 @@ aliyun:
     endpoint: oss-cn-hangzhou.aliyuncs.com
     accessKeyId: LTAI4Fi1qmL4iuhH8t7G7r2H
     accessKeySecret: 7tJWyQqa0cMkQGaPMMvtH6c0VLiOO2
-    bucketName: seec-helper-pdf
+    bucketName: seec-helper-dev
   sms:
     accessKeyId: LTAI4FiAXXyNfYJLweBXpKPx
     accessSecret: IBoC7KGkZHOvxZQIfQgp98ZY48AVjW
@@ -56,4 +60,4 @@ bok:
 helper:
   mail:
     from: ${spring.mail.username}
-    subject: seeccoder
+    subject: seecoder

+ 27 - 0
src/main/resources/update.sql

@@ -0,0 +1,27 @@
+alter table user modify id bigint auto_increment;
+
+alter table course modify id bigint auto_increment;
+alter table course modify teacher_id bigint not null;
+
+alter table choose modify id bigint auto_increment;
+alter table choose modify course_id bigint not null;
+alter table choose modify student_id bigint not null;
+
+alter table slide modify id bigint auto_increment;
+alter table slide modify course_id bigint not null;
+alter table slide modify teacher_id bigint not null;
+
+alter table message modify id bigint auto_increment;
+alter table message modify slide_id bigint;
+alter table message modify to_user_id bigint not null;
+
+alter table reply modify id bigint auto_increment;
+alter table reply modify teacher_id bigint not null;
+alter table reply drop foreign key FK6w0ns67lrq1jdiwi5xvtj1vxx;
+drop index FK6w0ns67lrq1jdiwi5xvtj1vxx on reply;
+alter table reply modify comment_id bigint not null;
+
+alter table comment modify id bigint auto_increment;
+alter table comment modify user_id bigint not null;
+alter table comment modify slide_id bigint not null;
+alter table comment modify content text not null;

+ 1 - 1
src/test/java/nju/seec/helper/HelperApplicationTests.java

@@ -3,7 +3,7 @@ package nju.seec.helper;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 
-@SpringBootTest
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
 class HelperApplicationTests {
 
     @Test