Преглед на файлове

feat: 文件评审相关功能

山山 преди 4 години
родител
ревизия
c5da026785
променени са 52 файла, в които са добавени 2031 реда и са изтрити 10 реда
  1. 13 1
      api/pom.xml
  2. 12 0
      common/pom.xml
  3. 12 0
      query/pom.xml
  4. 1 1
      web/pom.xml
  5. 0 1
      web/src/main/java/cn/seecoder/web/DevcloudWebApplication.java
  6. 203 0
      web/src/main/java/cn/seecoder/web/controller/code_review/CodeReviewController.java
  7. 70 0
      web/src/main/java/cn/seecoder/web/controller/code_review/CommentController.java
  8. 2 0
      web/src/main/java/cn/seecoder/web/controller/gitlab/CommitController.java
  9. 48 0
      web/src/main/java/cn/seecoder/web/controller/message/MessageController.java
  10. 41 0
      web/src/main/java/cn/seecoder/web/dao/code_review/BranchReviewMapper.java
  11. 24 0
      web/src/main/java/cn/seecoder/web/dao/code_review/CommentMapper.java
  12. 26 0
      web/src/main/java/cn/seecoder/web/dao/code_review/FileReviewMapper.java
  13. 27 0
      web/src/main/java/cn/seecoder/web/dao/code_review/InspectionResultMapper.java
  14. 30 0
      web/src/main/java/cn/seecoder/web/dao/message/MessageMapper.java
  15. 30 0
      web/src/main/java/cn/seecoder/web/infrastructure/config/WebSecurityConfig.java
  16. 3 0
      web/src/main/java/cn/seecoder/web/infrastructure/security/WebSecurityConstants.java
  17. 22 0
      web/src/main/java/cn/seecoder/web/model/enums/CodeReviewStatusEnum.java
  18. 26 0
      web/src/main/java/cn/seecoder/web/model/enums/CommentTypeEnum.java
  19. 24 0
      web/src/main/java/cn/seecoder/web/model/enums/FileReviewStatusEnum.java
  20. 20 0
      web/src/main/java/cn/seecoder/web/model/enums/MessageStatusEnum.java
  21. 4 1
      web/src/main/java/cn/seecoder/web/model/enums/UserIdentity.java
  22. 27 0
      web/src/main/java/cn/seecoder/web/model/po/code_review/CodeReviewPO.java
  23. 24 0
      web/src/main/java/cn/seecoder/web/model/po/code_review/CommentPO.java
  24. 49 0
      web/src/main/java/cn/seecoder/web/model/po/code_review/FileReviewPO.java
  25. 17 0
      web/src/main/java/cn/seecoder/web/model/po/code_review/InspectionResultPO.java
  26. 23 0
      web/src/main/java/cn/seecoder/web/model/po/message/MessagePO.java
  27. 41 0
      web/src/main/java/cn/seecoder/web/model/vo/code_review/CodeReviewCreateVO.java
  28. 25 0
      web/src/main/java/cn/seecoder/web/model/vo/code_review/CodeReviewSortedList.java
  29. 51 0
      web/src/main/java/cn/seecoder/web/model/vo/code_review/CodeReviewVO.java
  30. 35 0
      web/src/main/java/cn/seecoder/web/model/vo/code_review/CommentCreateVO.java
  31. 57 0
      web/src/main/java/cn/seecoder/web/model/vo/code_review/CommentVO.java
  32. 16 0
      web/src/main/java/cn/seecoder/web/model/vo/code_review/FileNodeCreateVO.java
  33. 25 0
      web/src/main/java/cn/seecoder/web/model/vo/code_review/FileNodeVO.java
  34. 33 0
      web/src/main/java/cn/seecoder/web/model/vo/code_review/FileReviewCreateVO.java
  35. 56 0
      web/src/main/java/cn/seecoder/web/model/vo/code_review/FileReviewVO.java
  36. 25 0
      web/src/main/java/cn/seecoder/web/model/vo/message/MessageCreateVO.java
  37. 45 0
      web/src/main/java/cn/seecoder/web/model/vo/message/MessageVO.java
  38. 38 0
      web/src/main/java/cn/seecoder/web/service/code_review/BranchReviewService.java
  39. 19 0
      web/src/main/java/cn/seecoder/web/service/code_review/CommentService.java
  40. 39 0
      web/src/main/java/cn/seecoder/web/service/code_review/FileReviewService.java
  41. 190 0
      web/src/main/java/cn/seecoder/web/service/impl/code_review/BranchReviewServiceImpl.java
  42. 92 0
      web/src/main/java/cn/seecoder/web/service/impl/code_review/CommentServiceImpl.java
  43. 188 0
      web/src/main/java/cn/seecoder/web/service/impl/code_review/FileReviewServiceImpl.java
  44. 63 0
      web/src/main/java/cn/seecoder/web/service/impl/message/MessageServiceImpl.java
  45. 18 0
      web/src/main/java/cn/seecoder/web/service/message/MessageService.java
  46. 3 3
      web/src/main/resources/application-wph.yml
  47. 1 1
      web/src/main/resources/application.yml
  48. 59 2
      web/src/main/resources/sql/table_init.sql
  49. 49 0
      web/src/test/java/cn/seecoder/web/service/code_review/BranchReviewServiceTest.java
  50. 34 0
      web/src/test/java/cn/seecoder/web/service/code_review/CommentServiceTest.java
  51. 31 0
      web/src/test/java/cn/seecoder/web/service/code_review/FileReviewServiceTest.java
  52. 20 0
      web/src/test/java/cn/seecoder/web/service/message/MessageServiceTest.java

+ 13 - 1
api/pom.xml

@@ -4,6 +4,18 @@
     <modelVersion>4.0.0</modelVersion>
     <artifactId>seecoder-devcloud-api</artifactId>
     <version>0.1.0</version>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
     <packaging>jar</packaging>
     <description>Demo project for Spring Boot</description>
 
@@ -37,7 +49,7 @@
                     <artifactId>log4j-slf4j-impl</artifactId>
                 </exclusion>
             </exclusions>
-            <version>0.1.0</version>
+            <version>0.1.2</version>
         </dependency>
         <!-- https://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit -->
         <dependency>

+ 12 - 0
common/pom.xml

@@ -4,6 +4,18 @@
     <modelVersion>4.0.0</modelVersion>
     <artifactId>seecoder-devcloud-common</artifactId>
     <version>0.1.0</version>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>6</source>
+                    <target>6</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
     <packaging>jar</packaging>
     <description>Demo project for Spring Boot</description>
 

+ 12 - 0
query/pom.xml

@@ -5,6 +5,18 @@
     <artifactId>seecoder-devcloud-query</artifactId>
     <version>0.1.0</version>
     <description>Demo project for Spring Boot</description>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 
     <parent>
         <groupId>cn.seecoder</groupId>

+ 1 - 1
web/pom.xml

@@ -130,7 +130,7 @@
         <dependency>
             <groupId>com.nju.edu</groupId>
             <artifactId>seecoder-gitlab-client</artifactId>
-            <version>0.1.0</version>
+            <version>0.1.2</version>
         </dependency>
     </dependencies>
 

+ 0 - 1
web/src/main/java/cn/seecoder/web/DevcloudWebApplication.java

@@ -17,7 +17,6 @@ import java.util.TimeZone;
 @EnableScheduling
 @EnableAsync
 @SpringBootApplication(exclude = {HibernateJpaAutoConfiguration.class})
-@ComponentScan(basePackages = {"cn.seecoder.web","cn.seecoder.query"})
 @MapperScan(basePackages = {"cn.seecoder.web","cn.seecoder.query"}, annotationClass = Repository.class)
 @EnableTransactionManagement //开始事务
 public class DevcloudWebApplication {

+ 203 - 0
web/src/main/java/cn/seecoder/web/controller/code_review/CodeReviewController.java

@@ -0,0 +1,203 @@
+package cn.seecoder.web.controller.code_review;
+
+import cn.seecoder.common.exceptions.ServiceException;
+import cn.seecoder.web.model.vo.Response;
+import cn.seecoder.web.model.vo.code_review.CodeReviewCreateVO;
+import cn.seecoder.web.model.vo.code_review.CodeReviewSortedList;
+import cn.seecoder.web.model.vo.code_review.CodeReviewVO;
+import cn.seecoder.web.model.vo.code_review.FileReviewCreateVO;
+import cn.seecoder.web.service.code_review.BranchReviewService;
+import cn.seecoder.web.service.code_review.FileReviewService;
+import com.nju.edu.gitlab.SeecoderGitlabApi;
+import com.nju.edu.gitlab.SeecoderGitlabException;
+import io.swagger.annotations.*;
+import org.gitlab4j.api.GitLabApiException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Zihe Chen
+ * @date 2022/1/12
+ * @description:
+ */
+@Api(tags = "Code Review相关Api")
+@RestController
+@RequestMapping("/code_review")
+public class CodeReviewController {
+    private final BranchReviewService branchReviewService;
+    private final FileReviewService fileReviewService;
+
+    @Autowired
+    public CodeReviewController(BranchReviewService branchReviewService, FileReviewService fileReviewService){
+        this.branchReviewService = branchReviewService;
+        this.fileReviewService = fileReviewService;
+    }
+
+    @GetMapping("/{projectId}")
+    @ApiOperation(value = "获取一个项目的所有code_review", httpMethod = "GET")
+    @ApiImplicitParam(value = "projectId", dataType = "int", paramType = "query")
+    public Response<CodeReviewSortedList> list(@PathVariable("projectId") Integer projectId){
+        return Response.buildSuccess(branchReviewService.list(projectId));
+    }
+
+    @PostMapping("/branch_review")
+    @ApiOperation(value = "创建一个code_review", httpMethod = "POST")
+    @ApiImplicitParam(value = "codeReviewCreateVO", dataType = "object", paramType = "body")
+    public Response createBranchReview(@RequestBody CodeReviewCreateVO codeReviewCreateVO){
+        try{
+            branchReviewService.create(codeReviewCreateVO);
+            return Response.buildSuccess();
+        } catch (RuntimeException exception) {
+            return Response.buildFailure(500, exception.getMessage());
+        }
+    }
+
+    @GetMapping("/branch_review/{codeReviewId}")
+    @ApiOperation(value = "获取某个code_review的详细信息", httpMethod = "GET")
+    @ApiImplicitParam(value = "codeReviewId", dataType = "int", paramType = "query")
+    public Response<CodeReviewVO> getBranchReviewById(@PathVariable("codeReviewId") Integer codeReviewId){
+        return Response.buildSuccess(branchReviewService.getById(codeReviewId));
+    }
+
+    @PutMapping("/close/{codeReviewId}")
+    @ApiOperation(value = "关闭某个code_review", httpMethod = "PUT")
+    @ApiImplicitParam(value = "codeReviewId", dataType = "int", paramType = "query")
+    public Response close(@PathVariable("codeReviewId") Integer codeReviewId){
+        branchReviewService.close(codeReviewId);
+        return Response.buildSuccess();
+    }
+
+    @PutMapping("/reopen/{codeReviewId}")
+    @ApiOperation(value = "重开某个code_review", httpMethod = "PUT")
+    @ApiImplicitParam(value = "codeReviewId", dataType = "int", paramType = "query")
+    public Response reopen(@PathVariable("codeReviewId") Integer codeReviewId){
+        branchReviewService.reopen(codeReviewId);
+        return Response.buildSuccess();
+    }
+
+    @PutMapping("/merge/{codeReviewId}")
+    @ApiOperation(value = "合并某个code_review", httpMethod = "PUT")
+    @ApiImplicitParam(value = "codeReviewId", dataType = "int", paramType = "query")
+    public Response merge(@PathVariable("codeReviewId") Integer codeReviewId) {
+        try{
+            branchReviewService.merge(codeReviewId);
+            return Response.buildSuccess();
+        } catch (SeecoderGitlabException | GitLabApiException exception) {
+            return Response.buildFailure(500, exception.getMessage());
+        }
+    }
+
+    @ApiOperation(value = "获取指定项目的所有branch信息", httpMethod = "GET")
+    @GetMapping("/branches/{projectId}")
+    @ApiImplicitParam(name = "projectId", dataType = "int", paramType = "query")
+    public Response getProjectBranches(@PathVariable("projectId") Integer projectId){
+        try{
+            return Response.buildSuccess(branchReviewService.getBranches(projectId));
+        } catch (SeecoderGitlabException exception) {
+            return Response.buildFailure(500, exception.getMessage());
+        }
+    }
+
+    @ApiOperation(value = "获取两个分支的比较信息", httpMethod = "GET")
+    @GetMapping("/compare")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "projectId", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "fromBash", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "toBash", dataType = "String", paramType = "query")
+    })
+    public Response compare(@RequestParam("projectId")Integer projectId, @RequestParam("fromBash")String fromBash,
+                            @RequestParam("toBash") String toBash){
+        try{
+            return Response.buildSuccess(branchReviewService.compareBranches(projectId, fromBash, toBash));
+        } catch (SeecoderGitlabException exception) {
+            return Response.buildFailure(500, exception.getMessage());
+        }
+    }
+
+    @PostMapping("/file_review")
+    @ApiOperation(value = "创建一个文件评审任务", httpMethod = "POST")
+    @ApiImplicitParam(value = "fileReviewCreateVO", dataType = "object", paramType = "body")
+    public Response createFileReview(@RequestBody FileReviewCreateVO fileReviewCreateVO){
+        fileReviewService.create(fileReviewCreateVO);
+        return Response.buildSuccess();
+    }
+
+    @PutMapping("/file_review")
+    @ApiOperation(value = "更新文件列表", httpMethod = "PUT")
+    @ApiImplicitParams({
+            @ApiImplicitParam(value = "fileReviewId", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(value = "fileList", dataType = "array", paramType = "query")
+    })
+    public Response updateFileList(@RequestParam("fileReviewId") int fileReviewId, @RequestParam("fileList") List<String> fileList){
+        fileReviewService.updateFileList(fileReviewId, fileList);
+        return Response.buildSuccess();
+    }
+
+    @GetMapping("/file_review/{fileReviewId}")
+    @ApiOperation(value = "获取一个文件评审任务详细信息", httpMethod = "GET")
+    @ApiImplicitParam(value = "fileReviewId", dataType = "int", paramType = "query")
+    public Response getByFileReviewId(@PathVariable("fileReviewId") int fileReviewId){
+        return Response.buildSuccess(fileReviewService.getById(fileReviewId));
+    }
+
+    @GetMapping("/repository_tree")
+    @ApiOperation(value = "获取仓库文件列表", httpMethod = "GET")
+    public Response getRepositoryTree(@RequestParam("projectId") int projectId, @RequestParam("branch") String branch,
+                                      @RequestParam(value = "path", required = false) String path) {
+        try {
+            if(path != null) return Response.buildSuccess(fileReviewService.getRepositoryTree(projectId, branch, path));
+            else return Response.buildSuccess(fileReviewService.getRepositoryTree(projectId, branch));
+        } catch (IOException exception) {
+            return Response.buildFailure(500, exception.getMessage());
+        }
+    }
+
+    @GetMapping("/invite")
+    @ApiOperation(value = "通过手机号码邀请评审人", httpMethod = "GET")
+    public Response inviteReviewer(@RequestParam("phoneNumber") String phoneNumber) {
+        try {
+            return Response.buildSuccess(fileReviewService.inviteUser(phoneNumber));
+        } catch (ServiceException exception) {
+            return Response.buildFailure(exception.getCode(), exception.getMessage());
+        }
+    }
+
+    @GetMapping("/file_content")
+    @ApiOperation(value = "获取某个文件的内容", httpMethod = "GET")
+    public Response getFileContent(@RequestParam("projectId") int projectId, @RequestParam("branch") String branch,
+                                   @RequestParam("path") String path) {
+        try{
+            return Response.buildSuccess(fileReviewService.getFile(projectId, branch, path));
+        } catch (SeecoderGitlabException exception) {
+            return Response.buildFailure(500, exception.getMessage());
+        }
+    }
+
+    @PutMapping("/inspect")
+    @ApiOperation(value = "将某个任务递交评审", httpMethod = "PUT")
+    public Response submitToInspect(@RequestParam("fileReviewId") int fileReviewId) {
+        fileReviewService.submitToInspection(fileReviewId);
+        return Response.buildSuccess();
+    }
+
+    @PutMapping("/rework")
+    @ApiOperation(value = "将某个任务要求返工修改", httpMethod = "PUT")
+    public Response submitToRework(@RequestParam("fileReviewId") int fileReviewId, @RequestParam("reviewerId") int reviewerId) {
+        return Response.buildSuccess(fileReviewService.submitInspection(fileReviewId, reviewerId, 0));
+    }
+
+    @PutMapping("/complete")
+    @ApiOperation(value = "将某一任务结束", httpMethod = "PUT")
+    public Response submitToComplete(@RequestParam("fileReviewId") int fileReviewId, @RequestParam("reviewerId") int reviewerId) {
+        return Response.buildSuccess(fileReviewService.submitInspection(fileReviewId, reviewerId, 1));
+    }
+
+    @GetMapping("/hasInspected")
+    @ApiOperation(value = "查看评审人员是否已提交结果", httpMethod = "GET")
+    public Response hasInspected(@RequestParam("fileReviewId") int fileReviewId, @RequestParam("reviewerId") int reviewerId) {
+        return Response.buildSuccess(fileReviewService.hasInspected(fileReviewId, reviewerId));
+    }
+}

+ 70 - 0
web/src/main/java/cn/seecoder/web/controller/code_review/CommentController.java

@@ -0,0 +1,70 @@
+package cn.seecoder.web.controller.code_review;
+
+import cn.seecoder.web.model.vo.Response;
+import cn.seecoder.web.model.vo.code_review.CommentCreateVO;
+import cn.seecoder.web.model.vo.code_review.CommentVO;
+import cn.seecoder.web.service.code_review.CommentService;
+import com.nju.edu.gitlab.SeecoderGitlabException;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Zihe Chen
+ * @date 2022/1/12
+ * @description:
+ */
+@Api(tags = "Comment相关Api")
+@RestController
+@RequestMapping("/comment")
+public class CommentController {
+    private final CommentService commentService;
+
+    @Autowired
+    public CommentController(CommentService commentService){
+        this.commentService = commentService;
+    }
+
+    @GetMapping("/{codeReviewId}")
+    @ApiOperation(value = "获取一个CodeReview的所有评论", httpMethod = "GET")
+    @ApiImplicitParam(value = "codeReviewId", dataType = "int", paramType = "query")
+    public Response list(@PathVariable("codeReviewId") Integer codeReviewId){
+        try{
+            return Response.buildSuccess(commentService.list(codeReviewId, false));
+        } catch (SeecoderGitlabException exception) {
+            return Response.buildFailure(500, exception.getMessage());
+        }
+    }
+
+    @GetMapping("/listForFileReview/{fileReviewId}")
+    @ApiOperation(value = "获取文件评审的所有评论", httpMethod = "GET")
+    @ApiImplicitParam(value = "fileReview", dataType = "int", paramType = "query")
+    public Response listForFileReview(@PathVariable("fileReviewId") Integer fileReviewId) {
+        try{
+            return Response.buildSuccess(commentService.list(fileReviewId, true));
+        } catch (SeecoderGitlabException exception) {
+            return Response.buildFailure(500, exception.getMessage());
+        }
+    }
+
+    @PostMapping("/create")
+    @ApiOperation(value = "创建一条新的Comment", httpMethod = "POST")
+    @ApiImplicitParam(value = "commentCreateVO", dataType = "object", paramType = "body")
+    public Response create(@RequestBody CommentCreateVO commentCreateVO){
+        commentService.create(commentCreateVO, false);
+        return Response.buildSuccess();
+    }
+
+    @PostMapping("/createForFileReview")
+    @ApiOperation(value = "为文件评审创建一条评论", httpMethod = "POST")
+    @ApiImplicitParam(value = "commentCreateVO", dataType = "object", paramType = "body")
+    public Response createForFileReview(@RequestBody CommentCreateVO commentCreateVO) {
+        commentService.create(commentCreateVO, true);
+        return Response.buildSuccess();
+    }
+}

+ 2 - 0
web/src/main/java/cn/seecoder/web/controller/gitlab/CommitController.java

@@ -12,6 +12,8 @@ import cn.seecoder.web.service.bug_list.BugListService;
 import cn.seecoder.web.service.project.CommitService;
 import cn.seecoder.web.service.tree.TreeNodeService;
 import com.nju.edu.gitlab.SeecoderGitlabApi;
+import com.nju.edu.gitlab.SeecoderGitlabException;
+import com.nju.edu.gitlab.vo.BranchVO;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiOperation;

+ 48 - 0
web/src/main/java/cn/seecoder/web/controller/message/MessageController.java

@@ -0,0 +1,48 @@
+package cn.seecoder.web.controller.message;
+
+import cn.seecoder.web.model.vo.Response;
+import cn.seecoder.web.model.vo.message.MessageVO;
+import cn.seecoder.web.service.message.MessageService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@Api(tags = "Message相关Api")
+@RestController
+@RequestMapping("/message")
+public class MessageController {
+    private final MessageService messageService;
+
+    @Autowired
+    public MessageController(MessageService messageService){
+        this.messageService = messageService;
+    }
+
+    @GetMapping("/{receiverId}")
+    @ApiOperation(value = "获取一个用户的新通知", httpMethod = "GET")
+    @ApiImplicitParam(value = "receiverId", dataType = "int", paramType = "query")
+    public Response<List<MessageVO>> getNewMessages(@PathVariable("receiverId") int receiverId){
+        return Response.buildSuccess(messageService.getNewMessages(receiverId));
+    }
+
+    @GetMapping("/listByUser")
+    @ApiOperation(value = "获取当前登录用户的消息")
+    public Response<List<MessageVO>> listByUser(){
+        return Response.buildSuccess(messageService.getMessagesByCurrentUser());
+    }
+
+    @GetMapping("/read/{messageId}")
+    @ApiOperation(value = "将某条消息置为已读")
+    @ApiImplicitParam(value = "messageId", dataType = "int", paramType = "query")
+    public Response readMessage(@PathVariable("messageId") int messageId){
+        messageService.readMessage(messageId);
+        return Response.buildSuccess();
+    }
+}

+ 41 - 0
web/src/main/java/cn/seecoder/web/dao/code_review/BranchReviewMapper.java

@@ -0,0 +1,41 @@
+package cn.seecoder.web.dao.code_review;
+
+import cn.seecoder.web.dao.provider.GeneralInsertUpdateSqlProvider;
+import cn.seecoder.web.model.po.code_review.CodeReviewPO;
+import org.apache.ibatis.annotations.InsertProvider;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface BranchReviewMapper {
+
+    @Select("select * from code_review where project_id = #{project_id}")
+    List<CodeReviewPO> selectByProjectId(int project_id);
+
+    @Select("select * from code_review where project_id = #{project_id} and status = \"OPENED\"")
+    List<CodeReviewPO> selectOpenedReviews(int project_id);
+
+    @Select("select * from code_review where project_id = #{project_id} and status = \"MERGED\"")
+    List<CodeReviewPO> selectMergedReviews(int project_id);
+
+    @Select("select * from code_review where project_id = #{project_id} and status = \"CLOSED\"")
+    List<CodeReviewPO> selectClosedReview(int project_id);
+
+    @InsertProvider(type = GeneralInsertUpdateSqlProvider.class, method = "insert")
+    int insertCodeReview(CodeReviewPO codeReviewPO, String... ignoredCols);
+
+    @Select("select * from code_review where id = #{id}")
+    CodeReviewPO selectById(int id);
+
+    @Update("update code_review set status=\"CLOSED\" where id = #{id}")
+    void close(int id);
+
+    @Update("update code_review set status = \"OPENED\" where id = #{id}")
+    void reopen(int id);
+
+    @Update("update code_review set status = \"MERGED\" where id = #{id}")
+    void merge(int id);
+}

+ 24 - 0
web/src/main/java/cn/seecoder/web/dao/code_review/CommentMapper.java

@@ -0,0 +1,24 @@
+package cn.seecoder.web.dao.code_review;
+
+import cn.seecoder.web.dao.provider.GeneralInsertUpdateSqlProvider;
+import cn.seecoder.web.model.po.code_review.CommentPO;
+import org.apache.ibatis.annotations.InsertProvider;
+import org.apache.ibatis.annotations.Options;
+import org.apache.ibatis.annotations.Select;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface CommentMapper {
+
+    @InsertProvider(type = GeneralInsertUpdateSqlProvider.class, method = "insert")
+    //@Options(useGeneratedKeys = true, keyProperty = "param1.id", keyColumn = "id")
+    int insert(CommentPO commentPO, String... ignoredCols);
+
+    @Select("select * from comment where code_review_id = #{id} and reply_id=-1")
+    List<CommentPO> selectByBranchReviewId(int id);
+
+    @Select("select * from comment where code_review_id = #{id} and reply_id=-2")
+    List<CommentPO> selectByFileReviewId(int id);
+}

+ 26 - 0
web/src/main/java/cn/seecoder/web/dao/code_review/FileReviewMapper.java

@@ -0,0 +1,26 @@
+package cn.seecoder.web.dao.code_review;
+
+import cn.seecoder.web.dao.provider.GeneralInsertUpdateSqlProvider;
+import cn.seecoder.web.model.po.code_review.FileReviewPO;
+import org.apache.ibatis.annotations.InsertProvider;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.UpdateProvider;
+import org.springframework.stereotype.Repository;
+
+import javax.swing.*;
+import java.util.List;
+
+@Repository
+public interface FileReviewMapper {
+    @Select("select * from file_review where project_id = #{projectId}")
+    List<FileReviewPO> selectByProjectId(int projectId);
+
+    @Select("select * from file_review where id = #{id}")
+    FileReviewPO selectById(int id);
+
+    @UpdateProvider(type = GeneralInsertUpdateSqlProvider.class, method = "updateById")
+    int updateById(FileReviewPO fileReviewPO);
+
+    @InsertProvider(type = GeneralInsertUpdateSqlProvider.class, method = "insert")
+    int insertFileReview(FileReviewPO fileReviewPO, String... ignoredCols);
+}

+ 27 - 0
web/src/main/java/cn/seecoder/web/dao/code_review/InspectionResultMapper.java

@@ -0,0 +1,27 @@
+package cn.seecoder.web.dao.code_review;
+
+import cn.seecoder.web.dao.provider.GeneralInsertUpdateSqlProvider;
+import cn.seecoder.web.model.po.code_review.InspectionResultPO;
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.InsertProvider;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.springframework.stereotype.Repository;
+import org.springframework.web.bind.annotation.PathVariable;
+
+import java.util.List;
+
+@Repository
+public interface InspectionResultMapper {
+    @Select("select * from inspection_result where file_review_id = #{fileReviewId}")
+    List<InspectionResultPO> selectByFileReviewId(int fileReviewId);
+
+    @Select("select * from inspection_result where file_review_id = #{fileReviewId} and reviewer_id = #{reviewerId}")
+    List<InspectionResultPO> selectByFileReviewIdAndReviewerId(@Param("fileReviewId") int fileReviewId, @Param("reviewerId") int reviewerId);
+
+    @InsertProvider(value = GeneralInsertUpdateSqlProvider.class, method = "insert")
+    int insertInspectionResult(InspectionResultPO inspectionResultPO, String... ignoredCols);
+
+    @Delete("delete from inspection_result where file_review_id = #{fileReviewId}")
+    int deleteByFileReviewId(int fileReviewId);
+}

+ 30 - 0
web/src/main/java/cn/seecoder/web/dao/message/MessageMapper.java

@@ -0,0 +1,30 @@
+package cn.seecoder.web.dao.message;
+
+import cn.seecoder.web.dao.provider.GeneralInsertUpdateSqlProvider;
+import cn.seecoder.web.model.po.message.MessagePO;
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.InsertProvider;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.UpdateProvider;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface MessageMapper {
+
+    @InsertProvider(type = GeneralInsertUpdateSqlProvider.class, method = "insert")
+    int insert(MessagePO messagePO, String... ignoredCols);
+
+    @Select("select * from message where receiver_id = #{id} and status = 'New'")
+    List<MessagePO> getNewMessages(int id);
+
+    @Delete("delete from message where create_time < #{time}")
+    int deleteMessage(String time);
+
+    @UpdateProvider(type = GeneralInsertUpdateSqlProvider.class, method = "updateById")
+    int update(MessagePO messagePO);
+
+    @Select("select * from message where id = #{id}")
+    MessagePO selectById(int id);
+}

+ 30 - 0
web/src/main/java/cn/seecoder/web/infrastructure/config/WebSecurityConfig.java

@@ -122,6 +122,36 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 // User Controller
                 .antMatchers(HttpMethod.GET, "/users/self").authenticated()
                 .antMatchers(HttpMethod.POST, "/users/gitlab").authenticated()
+                // CodeReviewController
+                .antMatchers(HttpMethod.GET, "/code_review/compare").authenticated()
+                .antMatchers(HttpMethod.GET, "/code_review/{projectId}").authenticated()
+                //BranchReviewRelated
+                .antMatchers(HttpMethod.POST, "/code_review/branch_review").authenticated()
+                .antMatchers(HttpMethod.GET, "/code_review/branches/{projectId}").authenticated()
+                .antMatchers(HttpMethod.GET, "/code_review/branch_review/{codeReviewId}").authenticated()
+                .antMatchers(HttpMethod.PUT, "/code_review/close/{codeReviewId}").authenticated()
+                .antMatchers(HttpMethod.PUT, "/code_review/reopen/{codeReviewId}").authenticated()
+                .antMatchers(HttpMethod.PUT, "/code_review/merge/{codeReviewId}").authenticated()
+                //FileReviewRelated
+                .antMatchers(HttpMethod.GET, "/code_review/file_review/{fileReviewId}").authenticated()
+                .antMatchers(HttpMethod.POST, "/code_review/file_review").authenticated()
+                .antMatchers(HttpMethod.GET, "/code_review/repository_tree").authenticated()
+                .antMatchers(HttpMethod.GET, "/code_review/invite").authenticated()
+                .antMatchers(HttpMethod.PUT,"/code_review/file_review").authenticated()
+                .antMatchers(HttpMethod.PUT, "/code_review/inspect").authenticated()
+                .antMatchers(HttpMethod.GET, "/code_review/file_content").authenticated()
+                .antMatchers(HttpMethod.PUT, "/code_review/rework").authenticated()
+                .antMatchers(HttpMethod.PUT, "/code_review/complete").authenticated()
+                .antMatchers(HttpMethod.GET, "/code_review/hasInspected").authenticated()
+                //CommentController
+                .antMatchers(HttpMethod.GET, "/comment/{codeReviewId}").authenticated()
+                .antMatchers(HttpMethod.GET, "/comment/listForFileReview/{fileReviewId}").authenticated()
+                .antMatchers(HttpMethod.POST, "/comment/create").authenticated()
+                .antMatchers(HttpMethod.POST, "/comment/createForFileReview").authenticated()
+                //MessageController
+                .antMatchers(HttpMethod.GET, "/message/{receiverId}").authenticated()
+                .antMatchers(HttpMethod.GET, "/message/listByUser").authenticated()
+                .antMatchers(HttpMethod.GET,"/message/read/{messageId}").authenticated()
                 // Swagger
                 .antMatchers("/**/*swagger*/**").permitAll()
                 .antMatchers("/**/*api-docs*/**").permitAll()

+ 3 - 0
web/src/main/java/cn/seecoder/web/infrastructure/security/WebSecurityConstants.java

@@ -17,10 +17,12 @@ public abstract class WebSecurityConstants {
 	public static final String STUDENT_ROLE = "STUDENT";
 	public static final String TEACHER_ROLE = "TEACHER";
 	public static final String ADMIN_ROLE = "ADMIN";
+	public static final String SEEC_ROLE = "SEEC";
 	
 	public static final GrantedAuthority STUDENT_AUTHORITY = new SimpleGrantedAuthority(ROLE_PREFIX + STUDENT_ROLE);
 	public static final GrantedAuthority TEACHER_AUTHORITY = new SimpleGrantedAuthority(ROLE_PREFIX + TEACHER_ROLE);
 	public static final GrantedAuthority ADMIN_AUTHORITY = new SimpleGrantedAuthority(ROLE_PREFIX + ADMIN_ROLE);
+	public static final GrantedAuthority SEEC_AUTHORITY = new SimpleGrantedAuthority(ROLE_PREFIX + SEEC_ROLE);
 
 
 
@@ -30,6 +32,7 @@ public abstract class WebSecurityConstants {
 		authorityTable.put(UserIdentity.STUDENT, STUDENT_AUTHORITY);
 		authorityTable.put(UserIdentity.TEACHER, TEACHER_AUTHORITY);
 		authorityTable.put(UserIdentity.ADMIN, ADMIN_AUTHORITY);
+		authorityTable.put(UserIdentity.SEEC, SEEC_AUTHORITY);
 	}
 	
 	public static GrantedAuthority getAuthority(UserIdentity identity){

+ 22 - 0
web/src/main/java/cn/seecoder/web/model/enums/CodeReviewStatusEnum.java

@@ -0,0 +1,22 @@
+package cn.seecoder.web.model.enums;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public enum CodeReviewStatusEnum {
+    @JsonProperty("Opened")
+    OPENED("Opened"),
+    @JsonProperty("Closed")
+    CLOSED("Closed"),
+    @JsonProperty("Merged")
+    MERGED("Merged");
+
+    private final String value;
+    CodeReviewStatusEnum(String value){
+        this.value = value;
+    }
+
+    @Override
+    public String toString(){
+        return value;
+    }
+}

+ 26 - 0
web/src/main/java/cn/seecoder/web/model/enums/CommentTypeEnum.java

@@ -0,0 +1,26 @@
+package cn.seecoder.web.model.enums;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public enum CommentTypeEnum {
+    @JsonProperty("Close")
+    CLOSE("Close"),
+    @JsonProperty("Merge")
+    MERGE("Merge"),
+    @JsonProperty("Reopen")
+    REOPEN("Reopen"),
+    @JsonProperty("Comment")
+    COMMENT("Comment"),
+    @JsonProperty("Reply")
+    REPLY("Reply"),
+    @JsonProperty("Commit")
+    COMMIT("Commit"),
+    @JsonProperty("Warning")
+    WARNING("Warning"),
+    @JsonProperty("Error")
+    ERROR("Error");
+    private final String value;
+    CommentTypeEnum(String value) { this.value = value; }
+    @Override
+    public String toString() { return this.value; }
+}

+ 24 - 0
web/src/main/java/cn/seecoder/web/model/enums/FileReviewStatusEnum.java

@@ -0,0 +1,24 @@
+package cn.seecoder.web.model.enums;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public enum FileReviewStatusEnum {
+    @JsonProperty("Planning")
+    PLANNING("Planning"),
+    @JsonProperty("Inspecting")
+    INSPECTING("Inspecting"),
+    @JsonProperty("Rework")
+    REWORK("Rework"),
+    @JsonProperty("Completed")
+    COMPLETED("Completed");
+
+    private final String value;
+    FileReviewStatusEnum(String value){
+        this.value = value;
+    }
+
+    @Override
+    public String toString(){
+        return value;
+    }
+}

+ 20 - 0
web/src/main/java/cn/seecoder/web/model/enums/MessageStatusEnum.java

@@ -0,0 +1,20 @@
+package cn.seecoder.web.model.enums;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public enum MessageStatusEnum {
+    @JsonProperty("New")
+    NEW("New"),
+    @JsonProperty("Read")
+    READ("Read");
+
+    private final String value;
+    MessageStatusEnum(String value){
+        this.value = value;
+    }
+
+    @Override
+    public String toString(){
+        return value;
+    }
+}

+ 4 - 1
web/src/main/java/cn/seecoder/web/model/enums/UserIdentity.java

@@ -11,7 +11,10 @@ public enum UserIdentity {
 	TEACHER("教师"),
 
 	@JsonProperty("管理员")
-	ADMIN("管理员");
+	ADMIN("管理员"),
+
+	@JsonProperty("开发人员")
+	SEEC("开发人员");
 
 	private final String name;
 

+ 27 - 0
web/src/main/java/cn/seecoder/web/model/po/code_review/CodeReviewPO.java

@@ -0,0 +1,27 @@
+package cn.seecoder.web.model.po.code_review;
+
+import cn.seecoder.web.model.enums.CodeReviewStatusEnum;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.sql.Timestamp;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CodeReviewPO {
+    private Integer id;
+    private Integer projectId;
+    private String sourceBranch;
+    private String targetBranch;
+    private Integer creatorId;
+    private Integer reviewerId;
+    private CodeReviewStatusEnum status;
+    private String title;
+    private String description;
+    private Boolean deleteSourceBranch;
+    private Timestamp createTime;
+}

+ 24 - 0
web/src/main/java/cn/seecoder/web/model/po/code_review/CommentPO.java

@@ -0,0 +1,24 @@
+package cn.seecoder.web.model.po.code_review;
+
+import cn.seecoder.web.model.enums.CommentTypeEnum;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.sql.Timestamp;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CommentPO {
+    private Integer id;
+    private Integer codeReviewId;
+    private Integer creatorId;
+    private String creatorName;
+    private String content;
+    private Timestamp time;
+    private CommentTypeEnum commentType;
+    private Integer replyId;
+}

+ 49 - 0
web/src/main/java/cn/seecoder/web/model/po/code_review/FileReviewPO.java

@@ -0,0 +1,49 @@
+package cn.seecoder.web.model.po.code_review;
+
+import cn.seecoder.web.model.enums.FileReviewStatusEnum;
+import cn.seecoder.web.model.vo.code_review.FileReviewCreateVO;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FileReviewPO {
+    private int id;
+    private int projectId;
+    private int creatorId;
+    private String reviewerId;
+    private String files;
+    private String branch;
+    private Timestamp createTime;
+    private String title;
+    private FileReviewStatusEnum status;
+
+    public FileReviewPO(FileReviewCreateVO fileReviewCreateVO){
+        this.setProjectId(fileReviewCreateVO.getProjectId());
+        this.setCreatorId(fileReviewCreateVO.getCreatorId());
+        this.setReviewerId(listToString(fileReviewCreateVO.getReviewerList()));
+        this.setFiles(listToString(fileReviewCreateVO.getFileList()));
+        this.setBranch(fileReviewCreateVO.getBranch());
+        this.setCreateTime(new Timestamp(System.currentTimeMillis()));
+        this.setTitle(fileReviewCreateVO.getTitle());
+        this.setStatus(FileReviewStatusEnum.PLANNING);
+    }
+
+    public static <T> String listToString(List<T> list){
+        if(list.size() == 1) return list.get(0).toString();
+        else {
+            StringBuilder result = new StringBuilder(list.get(0).toString());
+            for(int i = 1; i < list.size(); i++){
+                result.append("&").append(list.get(i).toString());
+            }
+            return result.toString();
+        }
+    }
+}

+ 17 - 0
web/src/main/java/cn/seecoder/web/model/po/code_review/InspectionResultPO.java

@@ -0,0 +1,17 @@
+package cn.seecoder.web.model.po.code_review;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InspectionResultPO {
+    private int id;
+    private int fileReviewId;
+    private int reviewerId;
+    private int pass;
+}

+ 23 - 0
web/src/main/java/cn/seecoder/web/model/po/message/MessagePO.java

@@ -0,0 +1,23 @@
+package cn.seecoder.web.model.po.message;
+
+import cn.seecoder.web.model.enums.MessageStatusEnum;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.sql.Timestamp;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class MessagePO {
+    private Integer id;
+    private Integer creatorId;
+    private Integer receiverId;
+    private String content;
+    private String linkTo;
+    private Timestamp createTime;
+    private MessageStatusEnum status;
+}

+ 41 - 0
web/src/main/java/cn/seecoder/web/model/vo/code_review/CodeReviewCreateVO.java

@@ -0,0 +1,41 @@
+package cn.seecoder.web.model.vo.code_review;
+
+import cn.seecoder.web.model.enums.CodeReviewStatusEnum;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@ApiModel("创建CodeReview表单")
+public class CodeReviewCreateVO {
+
+    @ApiModelProperty("关联的项目ID")
+    private Integer projectId;
+
+    @ApiModelProperty("合并源分支名称")
+    private String sourceBranch;
+
+    @ApiModelProperty("合并目标分支名称")
+    private String targetBranch;
+
+    @ApiModelProperty("创建人ID")
+    private Integer creatorId;
+
+    @ApiModelProperty("评审人ID")
+    private Integer reviewerId;
+
+    @ApiModelProperty("标题")
+    private String title;
+
+    @ApiModelProperty("描述")
+    private String description;
+
+    @ApiModelProperty("合并分支后是否删除源分支")
+    private Boolean deleteSourceBranch;
+
+}

+ 25 - 0
web/src/main/java/cn/seecoder/web/model/vo/code_review/CodeReviewSortedList.java

@@ -0,0 +1,25 @@
+package cn.seecoder.web.model.vo.code_review;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@ApiModel("CodeReview Sorted List")
+public class CodeReviewSortedList {
+    @ApiModelProperty("opened")
+    List<CodeReviewVO> opened;
+
+    @ApiModelProperty("merged")
+    List<CodeReviewVO> merged;
+
+    @ApiModelProperty("closed")
+    List<CodeReviewVO> closed;
+
+    @ApiModelProperty("fileReview")
+    List<FileReviewVO> fileReview;
+}

+ 51 - 0
web/src/main/java/cn/seecoder/web/model/vo/code_review/CodeReviewVO.java

@@ -0,0 +1,51 @@
+package cn.seecoder.web.model.vo.code_review;
+
+import cn.seecoder.web.model.enums.CodeReviewStatusEnum;
+import cn.seecoder.web.model.po.code_review.CodeReviewPO;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.beans.BeanUtils;
+
+import java.sql.Timestamp;
+
+@Data
+@ApiModel("CodeReview info")
+public class CodeReviewVO {
+    @ApiModelProperty("id")
+    private Integer id;
+
+    @ApiModelProperty("关联的项目ID")
+    private Integer projectId;
+
+    @ApiModelProperty("合并源分支名称")
+    private String sourceBranch;
+
+    @ApiModelProperty("合并目标分支名称")
+    private String targetBranch;
+
+    @ApiModelProperty("创建人ID")
+    private Integer creatorId;
+
+    @ApiModelProperty("评审人ID")
+    private Integer reviewerId;
+
+    @ApiModelProperty("CodeReview状态")
+    private CodeReviewStatusEnum status;
+
+    @ApiModelProperty("标题")
+    private String title;
+
+    @ApiModelProperty("描述")
+    private String description;
+
+    @ApiModelProperty("合并分支后是否删除源分支")
+    private Boolean deleteSourceBranch;
+
+    @ApiModelProperty("创建时间")
+    private Timestamp createTime;
+
+    public CodeReviewVO(CodeReviewPO codeReviewPO){
+        BeanUtils.copyProperties(codeReviewPO, this);
+    }
+}

+ 35 - 0
web/src/main/java/cn/seecoder/web/model/vo/code_review/CommentCreateVO.java

@@ -0,0 +1,35 @@
+package cn.seecoder.web.model.vo.code_review;
+
+import cn.seecoder.web.model.enums.CommentTypeEnum;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.sql.Timestamp;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("Comment 创建表单")
+public class CommentCreateVO {
+
+    @ApiModelProperty("关联的CodeReviewId")
+    private Integer codeReviewId;
+
+    @ApiModelProperty("创建人ID")
+    private Integer creatorId;
+
+    @ApiModelProperty("创建人名称")
+    private String creatorName;
+
+    @ApiModelProperty("评论内容")
+    private String content;
+
+    @ApiModelProperty("评论类型")
+    private CommentTypeEnum commentType;
+
+    @ApiModelProperty("若类型为回复,对应的评论ID")
+    private Integer replyId;
+}

+ 57 - 0
web/src/main/java/cn/seecoder/web/model/vo/code_review/CommentVO.java

@@ -0,0 +1,57 @@
+package cn.seecoder.web.model.vo.code_review;
+
+import cn.seecoder.web.model.enums.CommentTypeEnum;
+import cn.seecoder.web.model.po.code_review.CommentPO;
+import com.nju.edu.gitlab.vo.commit.CommitVO;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.beans.BeanUtils;
+
+import java.sql.Timestamp;
+
+@Data
+@ApiModel("Comment info")
+public class CommentVO implements Comparable<CommentVO> {
+    @ApiModelProperty("id")
+    private Integer id;
+
+    @ApiModelProperty("关联的CodeReviewId")
+    private Integer codeReviewId;
+
+    @ApiModelProperty("创建人ID")
+    private Integer creatorId;
+
+    @ApiModelProperty("创建人名称")
+    private String creatorName;
+
+    @ApiModelProperty("评论内容")
+    private String content;
+
+    @ApiModelProperty("创建时间")
+    private Timestamp time;
+
+    @ApiModelProperty("评论类型")
+    private CommentTypeEnum commentType;
+
+    @ApiModelProperty("若类型为回复,对应的评论ID")
+    private Integer replyId;
+
+    public CommentVO(CommentPO commentPO){
+        BeanUtils.copyProperties(commentPO, this);
+    }
+
+    public CommentVO(CommitVO commitVO) {
+        this.content = commitVO.getTitle();
+        this.creatorName = commitVO.getCommitterName();
+        this.setCommentType(CommentTypeEnum.COMMIT);
+        this.setTime(new Timestamp(commitVO.getCreatedAt().getTime()));
+    }
+
+    @Override
+    public int compareTo(CommentVO o) {
+        if(o.getTime().getTime() > this.getTime().getTime()) return -1;
+        else if(o.getTime().getTime() < this.getTime().getTime()) return 1;
+        else return 0;
+    }
+}

+ 16 - 0
web/src/main/java/cn/seecoder/web/model/vo/code_review/FileNodeCreateVO.java

@@ -0,0 +1,16 @@
+package cn.seecoder.web.model.vo.code_review;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class FileNodeCreateVO {
+    private String id;
+    private String name;
+    private String mode;
+    private String path;
+    private String type;
+}

+ 25 - 0
web/src/main/java/cn/seecoder/web/model/vo/code_review/FileNodeVO.java

@@ -0,0 +1,25 @@
+package cn.seecoder.web.model.vo.code_review;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class FileNodeVO {
+    private String name;
+    private String path;
+    private boolean leaf;
+
+    public FileNodeVO(FileNodeCreateVO node){
+        this.name = node.getName();
+        this.path = node.getPath();
+        if(node.getType().equals("tree")) {
+            this.leaf = false;
+        }
+        else {
+            this.leaf = true;
+        }
+    }
+}

+ 33 - 0
web/src/main/java/cn/seecoder/web/model/vo/code_review/FileReviewCreateVO.java

@@ -0,0 +1,33 @@
+package cn.seecoder.web.model.vo.code_review;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@ApiModel("创建FileReview表单")
+public class FileReviewCreateVO {
+    @ApiModelProperty("关联的项目Id")
+    private int projectId;
+
+    @ApiModelProperty("创建人Id")
+    private int creatorId;
+
+    @ApiModelProperty("评审人Id列表")
+    private List<Integer> reviewerList;
+
+    @ApiModelProperty("评审文件列表")
+    private List<String> fileList;
+
+    @ApiModelProperty("评审分支")
+    private String branch;
+
+    @ApiModelProperty("标题")
+    private String title;
+}

+ 56 - 0
web/src/main/java/cn/seecoder/web/model/vo/code_review/FileReviewVO.java

@@ -0,0 +1,56 @@
+package cn.seecoder.web.model.vo.code_review;
+
+import cn.seecoder.web.model.enums.FileReviewStatusEnum;
+import cn.seecoder.web.model.po.code_review.FileReviewPO;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Data
+@ApiModel("FileReview Info")
+public class FileReviewVO {
+    @ApiModelProperty("id")
+    private int id;
+
+    @ApiModelProperty("关联的项目Id")
+    private int projectId;
+
+    @ApiModelProperty("创建人Id")
+    private int creatorId;
+
+    @ApiModelProperty("评审人Id列表")
+    private List<Integer> reviewerList;
+
+    @ApiModelProperty("评审文件列表")
+    private List<String> fileList;
+
+    @ApiModelProperty("评审分支")
+    private String branch;
+
+    @ApiModelProperty("创建时间")
+    private Timestamp createTime;
+
+    @ApiModelProperty("标题")
+    private String title;
+
+    @ApiModelProperty("FileReview状态")
+    private FileReviewStatusEnum status;
+
+    public FileReviewVO(FileReviewPO fileReviewPO){
+        this.setId(fileReviewPO.getId());
+        this.setProjectId(fileReviewPO.getProjectId());
+        this.setCreatorId(fileReviewPO.getCreatorId());
+        this.setReviewerList(Arrays.asList(fileReviewPO.getReviewerId().split("&"))
+                .stream().map(item -> Integer.parseInt(item)).collect(Collectors.toList()));
+        this.setFileList(Arrays.asList(fileReviewPO.getFiles().split("&")));
+        this.setBranch(fileReviewPO.getBranch());
+        this.setCreateTime(fileReviewPO.getCreateTime());
+        this.setTitle(fileReviewPO.getTitle());
+        this.setStatus(fileReviewPO.getStatus());
+    }
+}

+ 25 - 0
web/src/main/java/cn/seecoder/web/model/vo/message/MessageCreateVO.java

@@ -0,0 +1,25 @@
+package cn.seecoder.web.model.vo.message;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@ApiModel("创建Message表单")
+public class MessageCreateVO {
+    @ApiModelProperty("发消息者ID")
+    private Integer creatorId;
+
+    @ApiModelProperty("接收者ID")
+    private Integer receiverId;
+
+    @ApiModelProperty("消息内容")
+    private String content;
+
+    @ApiModelProperty("跳转链接")
+    private String linkTo;
+}

+ 45 - 0
web/src/main/java/cn/seecoder/web/model/vo/message/MessageVO.java

@@ -0,0 +1,45 @@
+package cn.seecoder.web.model.vo.message;
+
+import cn.seecoder.web.model.enums.MessageStatusEnum;
+import cn.seecoder.web.model.po.message.MessagePO;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.beans.BeanUtils;
+
+import java.sql.Timestamp;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@ApiModel("Message info")
+public class MessageVO {
+    @ApiModelProperty("id")
+    private Integer id;
+
+    @ApiModelProperty("发消息者ID")
+    private Integer creatorId;
+
+    @ApiModelProperty("接收者ID")
+    private Integer receiverId;
+
+    @ApiModelProperty("内容")
+    private String content;
+
+    @ApiModelProperty("对应跳转链接")
+    private String linkTo;
+
+    @ApiModelProperty("创建时间")
+    private Timestamp createTime;
+
+    @ApiModelProperty("消息状态")
+    private MessageStatusEnum status;
+
+    public MessageVO(MessagePO messagePO){
+        BeanUtils.copyProperties(messagePO, this);
+    }
+}

+ 38 - 0
web/src/main/java/cn/seecoder/web/service/code_review/BranchReviewService.java

@@ -0,0 +1,38 @@
+package cn.seecoder.web.service.code_review;
+
+import cn.seecoder.web.model.vo.code_review.CodeReviewCreateVO;
+import cn.seecoder.web.model.vo.code_review.CodeReviewSortedList;
+import cn.seecoder.web.model.vo.code_review.CodeReviewVO;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.nju.edu.gitlab.SeecoderGitlabException;
+import com.nju.edu.gitlab.vo.BranchVO;
+import com.nju.edu.gitlab.vo.DiffVO;
+import org.gitlab4j.api.GitLabApiException;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Zihe Chen
+ * @date 2022/1/11
+ * @description:
+ */
+public interface BranchReviewService {
+
+    CodeReviewSortedList list(int projectId);
+
+    void create(CodeReviewCreateVO codeReviewCreateVO) throws RuntimeException;
+
+    CodeReviewVO getById(int id);
+
+    void close(int id);
+
+    void reopen(int id);
+
+    void merge(int id) throws GitLabApiException, SeecoderGitlabException;
+
+    List<BranchVO> getBranches(int projectId) throws SeecoderGitlabException;
+
+    Set<DiffVO> compareBranches(int projectId, String fromBash, String toBash) throws SeecoderGitlabException;
+}

+ 19 - 0
web/src/main/java/cn/seecoder/web/service/code_review/CommentService.java

@@ -0,0 +1,19 @@
+package cn.seecoder.web.service.code_review;
+
+import cn.seecoder.web.model.vo.code_review.CommentCreateVO;
+import cn.seecoder.web.model.vo.code_review.CommentVO;
+import com.nju.edu.gitlab.SeecoderGitlabException;
+
+import java.util.List;
+
+/**
+ * @author Zihe Chen
+ * @date 2022/1/11
+ * @description:
+ */
+public interface CommentService {
+
+    List<CommentVO> list(int codeReviewId, boolean isFileReview) throws SeecoderGitlabException;
+
+    void create(CommentCreateVO commentCreateVO, boolean isFileReview);
+}

+ 39 - 0
web/src/main/java/cn/seecoder/web/service/code_review/FileReviewService.java

@@ -0,0 +1,39 @@
+package cn.seecoder.web.service.code_review;
+
+import cn.seecoder.common.exceptions.ServiceException;
+import cn.seecoder.web.model.enums.FileReviewStatusEnum;
+import cn.seecoder.web.model.vo.code_review.FileNodeVO;
+import cn.seecoder.web.model.vo.code_review.FileReviewCreateVO;
+import cn.seecoder.web.model.vo.code_review.FileReviewVO;
+import cn.seecoder.web.model.vo.user.UserVO;
+import com.nju.edu.gitlab.SeecoderGitlabException;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Zihe Chen
+ * @date 2022/3/28
+ * @description:
+ */
+public interface FileReviewService {
+    void create(FileReviewCreateVO fileReviewCreateVO);
+
+    List<FileReviewVO> getByProjectId(int projectId);
+
+
+    FileReviewVO getById(int id);
+
+    String getFile(int projectId, String branch, String path) throws SeecoderGitlabException;
+
+    List<FileNodeVO> getRepositoryTree(int projectId, String branch, String... path) throws IOException;
+
+    UserVO inviteUser(String phoneNumber) throws ServiceException;
+
+    void updateFileList(int fileReviewId, List<String> fileList);
+
+    void submitToInspection(int fileReviewId);
+
+    FileReviewStatusEnum submitInspection(int fileReviewId, int reviewerId, int pass);
+
+    boolean hasInspected(int fileReviewId, int reviewerId);
+}

+ 190 - 0
web/src/main/java/cn/seecoder/web/service/impl/code_review/BranchReviewServiceImpl.java

@@ -0,0 +1,190 @@
+package cn.seecoder.web.service.impl.code_review;
+
+import cn.seecoder.web.dao.code_review.BranchReviewMapper;
+import cn.seecoder.web.model.enums.CodeReviewStatusEnum;
+import cn.seecoder.web.model.enums.CommentTypeEnum;
+import cn.seecoder.web.model.po.code_review.CodeReviewPO;
+import cn.seecoder.web.model.po.user.UserPO;
+import cn.seecoder.web.model.vo.code_review.*;
+import cn.seecoder.web.model.vo.message.MessageCreateVO;
+import cn.seecoder.web.service.code_review.BranchReviewService;
+import cn.seecoder.web.service.code_review.CommentService;
+import cn.seecoder.web.service.code_review.FileReviewService;
+import cn.seecoder.web.service.message.MessageService;
+import cn.seecoder.web.service.user.UserService;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nju.edu.gitlab.SeecoderGitlabApi;
+import com.nju.edu.gitlab.SeecoderGitlabException;
+import com.nju.edu.gitlab.vo.BranchVO;
+import com.nju.edu.gitlab.vo.DiffVO;
+import okhttp3.*;
+import org.gitlab4j.api.GitLabApi;
+import org.gitlab4j.api.GitLabApiException;
+import org.gitlab4j.api.models.MergeRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * @author Zihe Chen
+ * @date 2022/1/11
+ * @description:
+ */
+@Service
+public class BranchReviewServiceImpl implements BranchReviewService {
+    private final BranchReviewMapper branchReviewMapper;
+    private final MessageService messageService;
+    private final FileReviewService fileReviewService;
+    private final CommentService commentService;
+    private final SeecoderGitlabApi seecoderGitlabApi;
+    private OkHttpClient client;
+
+    @Autowired
+    public BranchReviewServiceImpl(BranchReviewMapper branchReviewMapper, MessageService messageService, FileReviewService fileReviewService,
+                                   CommentService commentService, SeecoderGitlabApi seecoderGitlabApi){
+        this.branchReviewMapper = branchReviewMapper;
+        this.messageService = messageService;
+        this.fileReviewService = fileReviewService;
+        this.commentService = commentService;
+        this.seecoderGitlabApi = seecoderGitlabApi;
+        client =  (new OkHttpClient.Builder()).connectTimeout(60L, TimeUnit.SECONDS).writeTimeout(60L, TimeUnit.SECONDS).retryOnConnectionFailure(true).build();
+    }
+
+    @Override
+    public CodeReviewSortedList list(int projectId) {
+        CodeReviewSortedList result = new CodeReviewSortedList();
+        result.setOpened(branchReviewMapper.selectOpenedReviews(projectId)
+                .stream().map(item -> new CodeReviewVO(item)).collect(Collectors.toList()));
+        result.setMerged(branchReviewMapper.selectMergedReviews(projectId)
+                .stream().map(item -> new CodeReviewVO(item)).collect(Collectors.toList()));
+        result.setClosed(branchReviewMapper.selectClosedReview(projectId)
+                .stream().map(item -> new CodeReviewVO(item)).collect(Collectors.toList()));
+        result.setFileReview(fileReviewService.getByProjectId(projectId));
+        return result;
+    }
+
+    @Override
+    public void create(CodeReviewCreateVO codeReviewCreateVO) throws RuntimeException {
+        CodeReviewPO codeReviewPO = new CodeReviewPO();
+        BeanUtils.copyProperties(codeReviewCreateVO, codeReviewPO);
+        List<CodeReviewPO> openedList = branchReviewMapper.selectOpenedReviews(codeReviewCreateVO.getProjectId());
+        for(CodeReviewPO temp : openedList) {
+            if(temp.getTargetBranch().equals(codeReviewCreateVO.getTargetBranch()) && temp.getSourceBranch().equals(codeReviewCreateVO.getSourceBranch())) {
+                throw new RuntimeException("已存在相同的代码评审任务,无法创建");
+            }
+        }
+        codeReviewPO.setStatus(CodeReviewStatusEnum.OPENED);
+        codeReviewPO.setCreateTime(new Timestamp(System.currentTimeMillis()));
+        int codeReviewId = branchReviewMapper.insertCodeReview(codeReviewPO, "id");
+        MessageCreateVO messageCreateVO = new MessageCreateVO();
+        messageCreateVO.setCreatorId(codeReviewCreateVO.getCreatorId());
+        messageCreateVO.setReceiverId(codeReviewCreateVO.getReviewerId());
+        messageCreateVO.setContent("有一个新的代码评审任务\""+codeReviewCreateVO.getTitle()+"\"");
+        messageCreateVO.setLinkTo("/project/"+codeReviewCreateVO.getProjectId()+"/codereviewdetail?codeReviewId="+codeReviewId);
+        messageService.create(messageCreateVO);
+    }
+
+    @Override
+    public CodeReviewVO getById(int id){
+        return new CodeReviewVO(branchReviewMapper.selectById(id));
+    }
+
+    @Override
+    public void close(int id) {
+        branchReviewMapper.close(id);
+        UserPO currentUser = UserService.loginUser();
+        CodeReviewVO codeReviewVO = this.getById(id);
+        CommentCreateVO commentCreateVO = new CommentCreateVO();
+        commentCreateVO.setCodeReviewId(id);
+        commentCreateVO.setCommentType(CommentTypeEnum.CLOSE);
+        commentCreateVO.setCreatorId(currentUser.getId());
+        commentCreateVO.setCreatorName(currentUser.getUsername());
+        commentCreateVO.setReplyId(-1);
+        commentService.create(commentCreateVO, false);
+        MessageCreateVO messageCreateVO = new MessageCreateVO();
+        messageCreateVO.setCreatorId(currentUser.getId());
+        messageCreateVO.setReceiverId(codeReviewVO.getCreatorId());
+        messageCreateVO.setContent("代码评审任务\""+codeReviewVO.getTitle()+"\"已被关闭");
+        messageCreateVO.setLinkTo("/project/"+codeReviewVO.getProjectId()+"/codereviewdetail?codeReviewId="+id);
+        messageService.create(messageCreateVO);
+    }
+
+    @Override
+    public void reopen(int id) {
+        branchReviewMapper.reopen(id);
+        UserPO currentUser = UserService.loginUser();
+        CodeReviewVO codeReviewVO = this.getById(id);
+        CommentCreateVO commentCreateVO = new CommentCreateVO();
+        commentCreateVO.setCodeReviewId(id);
+        commentCreateVO.setCommentType(CommentTypeEnum.REOPEN);
+        commentCreateVO.setCreatorId(currentUser.getId());
+        commentCreateVO.setCreatorName(currentUser.getUsername());
+        commentCreateVO.setReplyId(-1);
+        commentService.create(commentCreateVO, false);
+        MessageCreateVO messageCreateVO = new MessageCreateVO();
+        messageCreateVO.setCreatorId(currentUser.getId());
+        messageCreateVO.setReceiverId(codeReviewVO.getCreatorId());
+        messageCreateVO.setContent("代码评审任务\""+codeReviewVO.getTitle()+"\"已被重新开启");
+        messageCreateVO.setLinkTo("/project/"+codeReviewVO.getProjectId()+"/codereviewdetail?codeReviewId="+id);
+        messageService.create(messageCreateVO);
+    }
+
+    @Override
+    public void merge(int id) throws GitLabApiException, SeecoderGitlabException {
+        UserPO currentUser = UserService.loginUser();
+        CodeReviewPO codeReviewPO = branchReviewMapper.selectById(id);
+        String token;
+        try{
+            token = seecoderGitlabApi.getUserByUserNameNew(currentUser.getUsername()).getToken();
+        } catch (SeecoderGitlabException exception) {
+            throw new SeecoderGitlabException("500", "获取用户信息错误,可能是由于Gitlab账号与此账号名称不一致,请手动进行分支合并");
+        }
+        GitLabApi gitLabApi = new GitLabApi("https://seecoder-gitlab-gitlab.seec.seecoder.cn", token);
+        MergeRequest mergeRequest = gitLabApi.getMergeRequestApi().createMergeRequest(codeReviewPO.getProjectId(), codeReviewPO.getSourceBranch(), codeReviewPO.getTargetBranch(),
+                codeReviewPO.getTitle(), codeReviewPO.getDescription(), codeReviewPO.getReviewerId());
+        mergeRequest = gitLabApi.getMergeRequestApi().acceptMergeRequest(mergeRequest.getProjectId(), mergeRequest.getIid());
+
+        //数据库操作与对应的评论和消息创建
+        branchReviewMapper.merge(id);
+        CodeReviewVO codeReviewVO = this.getById(id);
+        CommentCreateVO commentCreateVO = new CommentCreateVO();
+        commentCreateVO.setCodeReviewId(id);
+        commentCreateVO.setCommentType(CommentTypeEnum.MERGE);
+        commentCreateVO.setCreatorId(currentUser.getId());
+        commentCreateVO.setCreatorName(currentUser.getUsername());
+        commentCreateVO.setReplyId(-1);
+        commentService.create(commentCreateVO, false);
+        MessageCreateVO messageCreateVO = new MessageCreateVO();
+        messageCreateVO.setCreatorId(currentUser.getId());
+        messageCreateVO.setReceiverId(codeReviewVO.getCreatorId());
+        messageCreateVO.setContent("代码评审任务\""+codeReviewVO.getTitle()+"\"已被合并");
+        messageCreateVO.setLinkTo("/project/"+codeReviewVO.getProjectId()+"/codereviewdetail?codeReviewId="+id);
+        messageService.create(messageCreateVO);
+    }
+
+    @Override
+    public List<BranchVO> getBranches(int projectId) throws SeecoderGitlabException {
+        return seecoderGitlabApi.getProjectBranches(projectId);
+    }
+
+    @Override
+    public Set<DiffVO> compareBranches(int projectId, String fromBash, String toBash) throws SeecoderGitlabException {
+//        ObjectMapper objectMapper = new ObjectMapper();
+//        Request request = new Request.Builder().url("https://seecoder-gitlab-server.seec.seecoder.cn/api/project/compare?projectId="+projectId+
+//                "&fromBash="+fromBash+"&toBash="+toBash).build();
+//        Response response = client.newCall(request).execute();
+//        return objectMapper.readTree(response.body().string());
+        return seecoderGitlabApi.compare(projectId, fromBash, toBash);
+    }
+}

+ 92 - 0
web/src/main/java/cn/seecoder/web/service/impl/code_review/CommentServiceImpl.java

@@ -0,0 +1,92 @@
+package cn.seecoder.web.service.impl.code_review;
+
+import cn.seecoder.web.dao.code_review.BranchReviewMapper;
+import cn.seecoder.web.dao.code_review.CommentMapper;
+import cn.seecoder.web.model.enums.CommentTypeEnum;
+import cn.seecoder.web.model.po.code_review.CodeReviewPO;
+import cn.seecoder.web.model.po.code_review.CommentPO;
+import cn.seecoder.web.model.vo.code_review.CommentCreateVO;
+import cn.seecoder.web.model.vo.code_review.CommentVO;
+import cn.seecoder.web.model.vo.message.MessageCreateVO;
+import cn.seecoder.web.service.code_review.CommentService;
+import cn.seecoder.web.service.code_review.FileReviewService;
+import cn.seecoder.web.service.message.MessageService;
+import com.nju.edu.gitlab.SeecoderGitlabApi;
+import com.nju.edu.gitlab.SeecoderGitlabException;
+import com.nju.edu.gitlab.vo.commit.CommitVO;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Zihe Chen
+ * @date 2022/1/11
+ * @description:
+ */
+@Service
+public class CommentServiceImpl implements CommentService {
+    private final CommentMapper commentMapper;
+    private final MessageService messageService;
+    private final BranchReviewMapper branchReviewMapper;
+    private final SeecoderGitlabApi seecoderGitlabApi;
+
+    @Autowired
+    public CommentServiceImpl(CommentMapper commentMapper, MessageService messageService,
+                              BranchReviewMapper branchReviewMapper, SeecoderGitlabApi seecoderGitlabApi, FileReviewService fileReviewService){
+        this.commentMapper = commentMapper;
+        this.messageService = messageService;
+        this.branchReviewMapper = branchReviewMapper;
+        this.seecoderGitlabApi = seecoderGitlabApi;
+    }
+
+    @Override
+    public List<CommentVO> list(int codeReviewId, boolean isFileReview) throws SeecoderGitlabException {
+        List<CommentVO> result = commentMapper.selectByFileReviewId(codeReviewId).stream().map(item -> new CommentVO(item)).collect(Collectors.toList());
+        if(!isFileReview){
+            result = commentMapper.selectByBranchReviewId(codeReviewId).stream().map(item -> new CommentVO(item)).collect(Collectors.toList());
+            CodeReviewPO codeReviewPO = branchReviewMapper.selectById(codeReviewId);
+            List<CommitVO> commits = seecoderGitlabApi.getCommitsByBranch(codeReviewPO.getProjectId(), codeReviewPO.getSourceBranch());
+            List<CommentVO> commitComment = commits.stream()
+                    .map(item -> new CommentVO(item))
+                    .filter(comment -> comment.getTime().after(codeReviewPO.getCreateTime()))
+                    .collect(Collectors.toList());
+            result.addAll(commitComment);
+        }
+        Collections.sort(result);
+        return result;
+    }
+
+    @Override
+    public void create(CommentCreateVO commentCreateVO, boolean isFileReview) {
+        CommentPO commentPO =  new CommentPO();
+        BeanUtils.copyProperties(commentCreateVO, commentPO);
+        commentPO.setTime(new Timestamp(System.currentTimeMillis()));
+        if(isFileReview) {
+            commentPO.setReplyId(-2);
+        }
+        else {
+            commentPO.setReplyId(-1);
+        }
+        commentMapper.insert(commentPO, "id");
+        if(!isFileReview){
+            if(!commentPO.getCommentType().equals(CommentTypeEnum.COMMENT)) return;
+            MessageCreateVO messageCreateVO = new MessageCreateVO();
+            messageCreateVO.setCreatorId(commentCreateVO.getCreatorId());
+            CodeReviewPO temp = branchReviewMapper.selectById(commentCreateVO.getCodeReviewId());
+            messageCreateVO.setReceiverId(temp.getCreatorId());
+            messageCreateVO.setContent("代码评审\""+ temp.getTitle() +"\"有一条新的评论");
+            messageCreateVO.setLinkTo("/project/"+temp.getProjectId()+"/codereviewdetail?codeReviewId="+temp.getId());
+            messageService.create(messageCreateVO);
+            if(!temp.getCreatorId().equals(temp.getReviewerId())){
+                messageCreateVO.setReceiverId(temp.getReviewerId());
+                messageService.create(messageCreateVO);
+            }
+        }
+    }
+
+}

+ 188 - 0
web/src/main/java/cn/seecoder/web/service/impl/code_review/FileReviewServiceImpl.java

@@ -0,0 +1,188 @@
+package cn.seecoder.web.service.impl.code_review;
+
+import cn.seecoder.common.exceptions.ServiceException;
+import cn.seecoder.web.dao.code_review.FileReviewMapper;
+import cn.seecoder.web.dao.code_review.InspectionResultMapper;
+import cn.seecoder.web.dao.user.UserMapper;
+import cn.seecoder.web.model.enums.FileReviewStatusEnum;
+import cn.seecoder.web.model.po.code_review.FileReviewPO;
+import cn.seecoder.web.model.po.code_review.InspectionResultPO;
+import cn.seecoder.web.model.vo.code_review.FileNodeCreateVO;
+import cn.seecoder.web.model.vo.code_review.FileNodeVO;
+import cn.seecoder.web.model.vo.code_review.FileReviewCreateVO;
+import cn.seecoder.web.model.vo.code_review.FileReviewVO;
+import cn.seecoder.web.model.vo.message.MessageCreateVO;
+import cn.seecoder.web.model.vo.user.UserVO;
+import cn.seecoder.web.service.code_review.FileReviewService;
+import cn.seecoder.web.service.message.MessageService;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nju.edu.gitlab.SeecoderGitlabApi;
+import com.nju.edu.gitlab.SeecoderGitlabException;
+import jnr.ffi.annotations.In;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.apache.http.HttpStatus;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@Service
+public class FileReviewServiceImpl implements FileReviewService {
+    private final SeecoderGitlabApi seecoderGitlabApi;
+    private final FileReviewMapper fileReviewMapper;
+    private final MessageService messageService;
+    private final UserMapper userMapper;
+    private final InspectionResultMapper inspectionResultMapper;
+    private OkHttpClient httpClient;
+
+    @Autowired
+    public FileReviewServiceImpl(SeecoderGitlabApi seecoderGitlabApi, FileReviewMapper fileReviewMapper,
+                                 MessageService messageService, UserMapper userMapper, InspectionResultMapper inspectionResultMapper){
+        this.seecoderGitlabApi = seecoderGitlabApi;
+        this.fileReviewMapper = fileReviewMapper;
+        this.messageService = messageService;
+        this.userMapper = userMapper;
+        this.inspectionResultMapper = inspectionResultMapper;
+        httpClient =  (new OkHttpClient.Builder()).connectTimeout(60L, TimeUnit.SECONDS).writeTimeout(60L, TimeUnit.SECONDS).retryOnConnectionFailure(true).build();
+    }
+
+    @Override
+    public void create(FileReviewCreateVO fileReviewCreateVO) {
+        FileReviewPO fileReviewPO = new FileReviewPO(fileReviewCreateVO);
+        int fileReviewId = fileReviewMapper.insertFileReview(fileReviewPO, "id");
+        MessageCreateVO messageCreateVO = new MessageCreateVO();
+        messageCreateVO.setCreatorId(fileReviewCreateVO.getCreatorId());
+        messageCreateVO.setContent("有一个新的文件评审任务\""+fileReviewCreateVO.getTitle()+"\"待处理");
+        messageCreateVO.setLinkTo("/project/"+fileReviewCreateVO.getProjectId()+"/filereviewdetail?fileReviewId="+fileReviewId);
+        for(Integer reviewerId : fileReviewCreateVO.getReviewerList()){
+            if(!reviewerId.equals(messageCreateVO.getCreatorId())){
+                messageCreateVO.setReceiverId(reviewerId);
+                messageService.create(messageCreateVO);
+            }
+        }
+    }
+
+    @Override
+    public List<FileReviewVO> getByProjectId(int projectId) {
+        return fileReviewMapper.selectByProjectId(projectId).stream().map(item -> new FileReviewVO(item)).collect(Collectors.toList());
+    }
+
+    @Override
+    public FileReviewVO getById(int id) {
+        return new FileReviewVO(fileReviewMapper.selectById(id));
+    }
+
+    @Override
+    public String getFile(int projectId, String branch, String path) throws SeecoderGitlabException {
+        return seecoderGitlabApi.getProjectFile(projectId, branch, path);
+    }
+
+    @Override
+    public List<FileNodeVO> getRepositoryTree(int projectId, String branch, String... path) throws IOException {
+        ObjectMapper objectMapper = new ObjectMapper();
+        StringBuilder stringBuilder = new StringBuilder("https://seecoder-gitlab-gitlab.seec.seecoder.cn/api/v4/projects/")
+                .append(projectId)
+                .append("/repository/tree?ref=")
+                .append(branch).append("&").append("path=");
+        if(path.length > 0) stringBuilder.append(path[0]);
+        Request request = new Request.Builder().url(stringBuilder.toString()).build();
+        Response response = httpClient.newCall(request).execute();
+        List<FileNodeCreateVO> result;
+        try {
+            result = objectMapper.readValue(response.body().string(), new TypeReference<List<FileNodeCreateVO>>(){});
+        } catch (IOException exception) {
+            throw new IOException("获取文件夹内容失败,请确保您的GitLab代码仓库权限设置为public");
+        }
+        return result.stream().map(item -> new FileNodeVO(item)).collect(Collectors.toList());
+    }
+
+    @Override
+    public UserVO inviteUser(String phoneNumber) throws ServiceException {
+        Integer id  = userMapper.selectIdByPhone(phoneNumber);
+        if(id == null){
+            throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "该手机号不存在对应用户");
+        }
+        return new UserVO(userMapper.select(id));
+    }
+
+    @Override
+    public void updateFileList(int fileReviewId, List<String> fileList) {
+        FileReviewPO fileReviewPO = fileReviewMapper.selectById(fileReviewId);
+        fileReviewPO.setFiles(FileReviewPO.listToString(fileList));
+        fileReviewMapper.updateById(fileReviewPO);
+    }
+
+    @Override
+    public void submitToInspection(int fileReviewId) {
+        FileReviewPO fileReviewPO = fileReviewMapper.selectById(fileReviewId);
+        fileReviewPO.setStatus(FileReviewStatusEnum.INSPECTING);
+        fileReviewMapper.updateById(fileReviewPO);
+        FileReviewVO fileReviewVO = new FileReviewVO(fileReviewPO);
+        MessageCreateVO messageCreateVO = new MessageCreateVO();
+        messageCreateVO.setCreatorId(fileReviewVO.getCreatorId());
+        messageCreateVO.setContent("文件评审任务\""+fileReviewVO.getTitle()+"\"已提交评审阶段,请及时处理");
+        messageCreateVO.setLinkTo("/project/"+fileReviewVO.getProjectId()+"/filereviewdetail?fileReviewId="+fileReviewId);
+        for(Integer reviewerId : fileReviewVO.getReviewerList()){
+            if(!reviewerId.equals(messageCreateVO.getCreatorId())){
+                messageCreateVO.setReceiverId(reviewerId);
+                messageService.create(messageCreateVO);
+            }
+        }
+    }
+
+    @Override
+    public FileReviewStatusEnum submitInspection(int fileReviewId, int reviewerId, int pass) {
+        //提交评审结果
+        InspectionResultPO inspectionResultPO = new InspectionResultPO();
+        inspectionResultPO.setFileReviewId(fileReviewId);
+        inspectionResultPO.setReviewerId(reviewerId);
+        inspectionResultPO.setPass(pass);
+        inspectionResultMapper.insertInspectionResult(inspectionResultPO, "id");
+        //判断评审是否结束以及评审结果
+        List<InspectionResultPO> list = inspectionResultMapper.selectByFileReviewId(fileReviewId);
+        FileReviewPO fileReviewPO = fileReviewMapper.selectById(fileReviewId);
+        FileReviewVO fileReviewVO = new FileReviewVO(fileReviewPO);
+        if(fileReviewVO.getReviewerList().size() == list.size()) {
+            //所有人都已完成评审
+            for(InspectionResultPO resultPO : list) {
+                if( resultPO.getPass() == 0 ){
+                    fileReviewPO.setStatus(FileReviewStatusEnum.REWORK);
+                    fileReviewMapper.updateById(fileReviewPO);
+                    inspectionResultMapper.deleteByFileReviewId(fileReviewId);
+                    MessageCreateVO messageCreateVO = new MessageCreateVO();
+                    messageCreateVO.setCreatorId(reviewerId);
+                    messageCreateVO.setReceiverId(fileReviewVO.getCreatorId());
+                    messageCreateVO.setContent("文件评审\""+fileReviewVO.getTitle()+"\"已被要求返工,请及时修改");
+                    messageCreateVO.setLinkTo("/project/"+fileReviewVO.getProjectId()+"/filereviewdetail?fileReviewId="+fileReviewId);
+                    messageService.create(messageCreateVO);
+                    return FileReviewStatusEnum.REWORK;
+                }
+            }
+            fileReviewPO.setStatus(FileReviewStatusEnum.COMPLETED);
+            fileReviewMapper.updateById(fileReviewPO);
+            inspectionResultMapper.deleteByFileReviewId(fileReviewId);
+            MessageCreateVO messageCreateVO = new MessageCreateVO();
+            messageCreateVO.setCreatorId(reviewerId);
+            messageCreateVO.setReceiverId(fileReviewVO.getCreatorId());
+            messageCreateVO.setContent("文件评审\""+fileReviewVO.getTitle()+"\"已通过评审");
+            messageCreateVO.setLinkTo("/project/"+fileReviewVO.getProjectId()+"/filereviewdetail?fileReviewId="+fileReviewId);
+            messageService.create(messageCreateVO);
+            return FileReviewStatusEnum.COMPLETED;
+        }
+        //还有评审人未完成评审
+        return FileReviewStatusEnum.INSPECTING;
+    }
+
+    @Override
+    public boolean hasInspected(int fileReviewId, int reviewerId) {
+        List<InspectionResultPO> list = inspectionResultMapper.selectByFileReviewIdAndReviewerId(fileReviewId, reviewerId);
+        return !(list.size() == 0);
+    }
+}

+ 63 - 0
web/src/main/java/cn/seecoder/web/service/impl/message/MessageServiceImpl.java

@@ -0,0 +1,63 @@
+package cn.seecoder.web.service.impl.message;
+
+import cn.seecoder.web.dao.message.MessageMapper;
+import cn.seecoder.web.model.enums.MessageStatusEnum;
+import cn.seecoder.web.model.po.message.MessagePO;
+import cn.seecoder.web.model.vo.message.MessageCreateVO;
+import cn.seecoder.web.model.vo.message.MessageVO;
+import cn.seecoder.web.service.message.MessageService;
+import cn.seecoder.web.service.user.UserService;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class MessageServiceImpl implements MessageService {
+    private final MessageMapper messageMapper;
+    private final UserService userService;
+
+    @Autowired
+    public MessageServiceImpl(MessageMapper messageMapper, UserService userService){
+        this.messageMapper = messageMapper;
+        this.userService = userService;
+    }
+
+    @Override
+    public List<MessageVO> getNewMessages(int receiverId) {
+        return messageMapper.getNewMessages(receiverId).stream().map(item -> new MessageVO(item)).collect(Collectors.toList());
+    }
+
+    @Override
+    public List<MessageVO> getMessagesByCurrentUser() {
+        int userId = userService.getLoginUser().getId();
+        return messageMapper.getNewMessages(userId).stream().map(item -> new MessageVO(item)).collect(Collectors.toList());
+    }
+
+    @Override
+    public void create(MessageCreateVO messageCreateVO) {
+        MessagePO messagePO = new MessagePO();
+        BeanUtils.copyProperties(messageCreateVO, messagePO);
+        messagePO.setCreateTime(new Timestamp(System.currentTimeMillis()));
+        messagePO.setStatus(MessageStatusEnum.NEW);
+        messageMapper.insert(messagePO, "id");
+    }
+
+    @Override
+    @Scheduled(cron = "0 0 0 * * ?")
+    public void deleteMessage() {
+        Timestamp time = new Timestamp(System.currentTimeMillis() - (long) 15 * 24 * 60 * 60 * 1000);
+        messageMapper.deleteMessage(time.toString());
+    }
+
+    @Override
+    public void readMessage(int id) {
+        MessagePO messagePO = messageMapper.selectById(id);
+        messagePO.setStatus(MessageStatusEnum.READ);
+        messageMapper.update(messagePO);
+    }
+}

+ 18 - 0
web/src/main/java/cn/seecoder/web/service/message/MessageService.java

@@ -0,0 +1,18 @@
+package cn.seecoder.web.service.message;
+
+import cn.seecoder.web.model.vo.message.MessageCreateVO;
+import cn.seecoder.web.model.vo.message.MessageVO;
+
+import java.util.List;
+
+public interface MessageService {
+    List<MessageVO> getNewMessages(int receiverId);
+
+    List<MessageVO> getMessagesByCurrentUser();
+
+    void create(MessageCreateVO messageCreateVO);
+
+    void deleteMessage();
+
+    void readMessage(int id);
+}

+ 3 - 3
web/src/main/resources/application-wph.yml

@@ -1,8 +1,8 @@
 spring:
   datasource:
-    url: jdbc:mysql://192.168.99.105:30006/devcloud?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
+    url: jdbc:mysql://localhost:3306/devcloud?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
     username: root
-    password: root
+    password: czh13935748710.
     sql-script-encoding: utf-8
     separator: ;
     initialization-mode: always
@@ -40,7 +40,7 @@ seecoder:
     debugging: true
     ingressHostSuffix: .devcloud.seec.seecoder.cn
   gitlab:
-    host: http://seecoder-gitlab-server.seec.seecoder.cn
+    host: https://seecoder-gitlab-server.seec.seecoder.cn
     webhookProxy: http://environment-21-70.seec.svc.cluster.local:8080/hook
     token: wXDeETv2Kmt-JhiApJ9H
   git:

+ 1 - 1
web/src/main/resources/application.yml

@@ -1,3 +1,3 @@
 spring:
   profiles:
-    active: prod
+    active: wph

+ 59 - 2
web/src/main/resources/sql/table_init.sql

@@ -212,6 +212,63 @@ create table if not exists devcloud.pipeline_record
     constraint pipeline_record_user_id_fk
         foreign key (user_id) references devcloud.user (id)
             on delete set null
-)
-    charset = utf8;
+)charset = utf8;
+
+create table if not exists devcloud.code_review
+(
+    id int auto_increment primary key ,
+    project_id int not null ,
+    source_branch varchar(40) not null,
+    target_branch varchar(40) not null,
+    creator_id int not null,
+    reviewer_id int not null,
+    status varchar(10) default '0' not null,
+    title varchar(20) not null,
+    description varchar(255) null,
+    delete_source_branch boolean default 0 null,
+    create_time timestamp default current_timestamp not null
+)charset = utf8;
+
+create table if not exists devcloud.file_review
+(
+    id int auto_increment primary key ,
+    project_id int not null,
+    creator_id int not null ,
+    reviewer_id text not null ,
+    files text not null ,
+    branch varchar(40) not null ,
+    create_time timestamp default current_timestamp not null ,
+    title varchar(20) not null ,
+    status varchar(15) not null
+)charset = utf8;
 
+create table if not exists devcloud.comment
+(
+    id int auto_increment primary key ,
+    code_review_id int not null ,
+    creator_id int not null,
+    creator_name varchar(30),
+    content text,
+    time timestamp default current_timestamp not null,
+    comment_type varchar(10) default 'Comment' not null,
+    reply_id int default '0' not null
+)charset = utf8;
+
+create table if not exists devcloud.message
+(
+    id int auto_increment primary key ,
+    creator_id int not null ,
+    receiver_id int not null ,
+    content text,
+    link_to text not null,
+    create_time timestamp default current_timestamp not null ,
+    status varchar(10) default 'New' not null
+)charset = utf8;
+
+create table if not exists devcloud.inspection_result
+(
+    id int auto_increment primary key ,
+    file_review_id int not null ,
+    reviewer_id int not null,
+    pass int not null
+)charset = utf8;

+ 49 - 0
web/src/test/java/cn/seecoder/web/service/code_review/BranchReviewServiceTest.java

@@ -0,0 +1,49 @@
+package cn.seecoder.web.service.code_review;
+
+import cn.seecoder.web.DevcloudWebApplication;
+import com.nju.edu.gitlab.SeecoderGitlabException;
+import org.gitlab4j.api.GitLabApiException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.io.IOException;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = DevcloudWebApplication.class)
+public class BranchReviewServiceTest {
+    @Autowired
+    BranchReviewService branchReviewService;
+
+    @Test
+    public void closeTest(){
+        branchReviewService.close(1);
+    }
+
+    @Test
+    public void reopenTest(){
+        branchReviewService.reopen(1);
+    }
+
+    @Test
+    public void branchTest(){
+        try{
+            System.out.println(branchReviewService.getBranches(4302));
+        } catch (SeecoderGitlabException exception) {
+            System.out.println(exception.getMessage());
+        }
+    }
+
+    @Test
+    public void mergeTest() {
+        try {
+            branchReviewService.merge(9);
+        } catch (GitLabApiException exception) {
+            System.out.println("exception " + exception.getMessage() + exception.getReason());
+        } catch (SeecoderGitlabException exception) {
+            System.out.println(exception.getMessage());
+        }
+    }
+}

+ 34 - 0
web/src/test/java/cn/seecoder/web/service/code_review/CommentServiceTest.java

@@ -0,0 +1,34 @@
+package cn.seecoder.web.service.code_review;
+
+import cn.seecoder.web.DevcloudWebApplication;
+import cn.seecoder.web.model.enums.CommentTypeEnum;
+import cn.seecoder.web.model.vo.code_review.CommentCreateVO;
+import com.nju.edu.gitlab.SeecoderGitlabException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = DevcloudWebApplication.class)
+public class CommentServiceTest {
+    @Autowired
+    CommentService commentService;
+
+    @Test
+    public void createTest(){
+        CommentCreateVO commentCreateVO = new CommentCreateVO(1, 1,"ShanShan", "comment", CommentTypeEnum.COMMENT, 0);
+        commentService.create(commentCreateVO, false);
+    }
+
+    @Test
+    public void getTest(){
+        try{
+            System.out.println(commentService.list(5, false));
+        } catch (SeecoderGitlabException exception){
+            System.out.println(exception.getMessage());
+        }
+    }
+
+}

+ 31 - 0
web/src/test/java/cn/seecoder/web/service/code_review/FileReviewServiceTest.java

@@ -0,0 +1,31 @@
+package cn.seecoder.web.service.code_review;
+
+import cn.seecoder.web.DevcloudWebApplication;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.io.IOException;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = DevcloudWebApplication.class)
+public class FileReviewServiceTest {
+    @Autowired
+    FileReviewService fileReviewService;
+
+    @Test
+    public void getRepositoryTreeTest() {
+        try {
+            System.out.println(fileReviewService.getRepositoryTree(245,"dev"));
+        } catch (IOException exception) {
+            System.out.println(exception.getMessage());
+        }
+    }
+
+    @Test
+    public void submitInspectionTest() {
+        System.out.println(fileReviewService.submitInspection(2, 1594, 1));
+    }
+}

+ 20 - 0
web/src/test/java/cn/seecoder/web/service/message/MessageServiceTest.java

@@ -0,0 +1,20 @@
+package cn.seecoder.web.service.message;
+
+import cn.seecoder.web.DevcloudWebApplication;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = DevcloudWebApplication.class)
+public class MessageServiceTest {
+    @Autowired
+    MessageService messageService;
+
+    @Test
+    public void deleteTest(){
+        messageService.deleteMessage();
+    }
+}