Kaynağa Gözat

考试系统插件提交

pans 8 ay önce
ebeveyn
işleme
124219e3fd
100 değiştirilmiş dosya ile 6772 ekleme ve 1 silme
  1. 111 1
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/pom.xml
  2. 39 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/base/BaseApiController.java
  3. 12 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/base/BaseEntity.java
  4. 50 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/base/BasePage.java
  5. 124 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/base/RestResponse.java
  6. 71 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/base/SystemCode.java
  7. 49 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/application/ApplicationContextProvider.java
  8. 28 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/property/CookieConfig.java
  9. 50 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/property/PasswordKeyConfig.java
  10. 92 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/property/QnConfig.java
  11. 94 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/property/SystemConfig.java
  12. 93 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/property/WxConfig.java
  13. 73 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/exception/ExceptionHandle.java
  14. 70 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/mvc/WebMvcConfiguration.java
  15. 68 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/AuthenticationBean.java
  16. 35 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/LoginAuthenticationEntryPoint.java
  17. 26 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestAccessDeniedHandler.java
  18. 27 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestAuthenticationFailureHandler.java
  19. 83 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestAuthenticationProvider.java
  20. 64 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestAuthenticationSuccessHandler.java
  21. 52 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestDetailsServiceImpl.java
  22. 67 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestLoginAuthenticationFilter.java
  23. 54 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestLogoutSuccessHandler.java
  24. 31 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestTokenBasedRememberMeServices.java
  25. 63 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestUtil.java
  26. 136 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/SecurityConfigurer.java
  27. 68 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/wx/TokenHandlerInterceptor.java
  28. 63 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/context/WebContext.java
  29. 50 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/context/WxContext.java
  30. 40 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/ErrorController.java
  31. 56 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/DashboardController.java
  32. 69 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/EducationController.java
  33. 57 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/ExamPaperAnswerController.java
  34. 75 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/ExamPaperController.java
  35. 85 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/MessageController.java
  36. 104 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/QuestionController.java
  37. 63 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/TaskController.java
  38. 103 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/UploadController.java
  39. 152 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/UserController.java
  40. 128 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/DashboardController.java
  41. 47 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/EducationController.java
  42. 120 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/ExamPaperAnswerController.java
  43. 53 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/ExamPaperController.java
  44. 73 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/QuestionAnswerController.java
  45. 18 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/QuestionController.java
  46. 51 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/UploadController.java
  47. 140 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/UserController.java
  48. 22 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/wx/BaseWXApiController.java
  49. 85 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/wx/student/AuthController.java
  50. 129 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/wx/student/DashboardController.java
  51. 135 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/wx/student/ExamPaperAnswerController.java
  52. 57 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/wx/student/ExamPaperController.java
  53. 150 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/wx/student/UserController.java
  54. 189 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/ExamPaper.java
  55. 195 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/ExamPaperAnswer.java
  56. 34 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/ExamPaperAnswerInfo.java
  57. 195 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/ExamPaperQuestionCustomerAnswer.java
  58. 123 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/Message.java
  59. 107 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/MessageUser.java
  60. 174 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/Question.java
  61. 80 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/Subject.java
  62. 107 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/TaskExam.java
  63. 71 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/TaskExamCustomerAnswer.java
  64. 54 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/TextContent.java
  65. 216 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/User.java
  66. 96 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/UserEventLog.java
  67. 97 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/UserToken.java
  68. 49 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/enums/ExamPaperAnswerStatusEnum.java
  69. 51 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/enums/ExamPaperTypeEnum.java
  70. 34 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/enums/QuestionStatusEnum.java
  71. 63 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/enums/QuestionTypeEnum.java
  72. 52 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/enums/RoleEnum.java
  73. 48 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/enums/UserStatusEnum.java
  74. 22 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/exam/ExamPaperQuestionItemObject.java
  75. 27 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/exam/ExamPaperTitleItemObject.java
  76. 32 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/other/ExamPaperAnswerUpdate.java
  77. 24 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/other/KeyValue.java
  78. 45 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/question/QuestionItemObject.java
  79. 48 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/question/QuestionObject.java
  80. 42 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/task/TaskItemAnswerObject.java
  81. 32 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/task/TaskItemObject.java
  82. 37 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/event/CalculateExamPaperAnswerCompleteEvent.java
  83. 37 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/event/OnRegistrationCompleteEvent.java
  84. 34 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/event/UserEvent.java
  85. 125 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/exception/BusinessException.java
  86. 82 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/listener/CalculateExamPaperAnswerListener.java
  87. 25 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/listener/EmailSendListener.java
  88. 35 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/listener/UserLogListener.java
  89. 16 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/BaseMapper.java
  90. 25 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/ExamPaperAnswerMapper.java
  91. 33 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/ExamPaperMapper.java
  92. 27 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/ExamPaperQuestionCustomerAnswerMapper.java
  93. 17 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/MessageMapper.java
  94. 19 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/MessageUserMapper.java
  95. 22 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/QuestionMapper.java
  96. 17 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/SubjectMapper.java
  97. 15 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/TaskExamCustomerAnswerMapper.java
  98. 15 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/TaskExamMapper.java
  99. 9 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/TextContentMapper.java
  100. 20 0
      snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/UserEventLogMapper.java

+ 111 - 1
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/pom.xml

@@ -9,12 +9,122 @@
         <artifactId>snowy-plugin-exam</artifactId>
         <version>2.0.0</version>
     </parent>
-
+    <properties>
+        <spring.boot.version>2.5.12</spring.boot.version>
+    </properties>
     <artifactId>snowy-plugin-exam-func</artifactId>
     <packaging>jar</packaging>
     <description>考试系统插件func实现</description>
 
     <dependencies>
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-common</artifactId>
+            <version>${project.parent.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.fhs-opensource</groupId>
+                    <artifactId>easy-trans-spring-boot-starter</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.fhs-opensource</groupId>
+                    <artifactId>easy-trans-mybatis-plus-extend</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+            <version>${spring.boot.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+            <version>${spring.boot.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <version>${spring.boot.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-tomcat</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-undertow</artifactId>
+            <version>${spring.boot.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+            <version>2.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+            <version>1.2.12</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>${mysql.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+            <version>2.6.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.9</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.modelmapper</groupId>
+            <artifactId>modelmapper</artifactId>
+            <version>2.3.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.9</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.qiniu</groupId>
+            <artifactId>qiniu-java-sdk</artifactId>
+            <version>7.2.25</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <version>${spring.boot.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-test</artifactId>
+            <scope>test</scope>
+        </dependency>
 
+        <!-- hot debug -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <version>${spring.boot.version}</version>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
     </dependencies>
 </project>

+ 39 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/base/BaseApiController.java

@@ -0,0 +1,39 @@
+package vip.xiaonuo.exam.base;
+
+
+import vip.xiaonuo.exam.context.WebContext;
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.utility.ModelMapperSingle;
+import org.modelmapper.ModelMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * @version 3.3.0
+ * @description: The type Base api controller.
+ * Copyright (C), 2019-2024, 武汉思维跳跃科技有限公司
+ * @date 2021 /5/26 10:45
+ */
+public class BaseApiController {
+    /**
+     * The constant DEFAULT_PAGE_SIZE.
+     */
+    protected final static String DEFAULT_PAGE_SIZE = "10";
+    /**
+     * The constant modelMapper.
+     */
+    protected final static ModelMapper modelMapper = ModelMapperSingle.Instance();
+    /**
+     * The Web context.
+     */
+    @Autowired
+    protected WebContext webContext;
+
+    /**
+     * Gets current user.
+     *
+     * @return the current user
+     */
+    protected User getCurrentUser() {
+        return webContext.getCurrentUser();
+    }
+}

+ 12 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/base/BaseEntity.java

@@ -0,0 +1,12 @@
+package vip.xiaonuo.exam.base;
+
+
+/**
+ * @version 3.3.0
+ * @description: The type Base entity.
+ * Copyright (C), 2019-2024, 武汉思维跳跃科技有限公司
+ * @date 2021 /5/26 10:45
+ */
+public abstract class BaseEntity {
+
+}

+ 50 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/base/BasePage.java

@@ -0,0 +1,50 @@
+package vip.xiaonuo.exam.base;
+
+/**
+ * @version 3.3.0
+ * @description: The type Base page.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/5/25 10:45
+ */
+public class BasePage {
+
+    private Integer pageIndex;
+
+    private Integer pageSize;
+
+    /**
+     * Gets page index.
+     *
+     * @return the page index
+     */
+    public Integer getPageIndex() {
+        return pageIndex;
+    }
+
+    /**
+     * Sets page index.
+     *
+     * @param pageIndex the page index
+     */
+    public void setPageIndex(Integer pageIndex) {
+        this.pageIndex = pageIndex;
+    }
+
+    /**
+     * Gets page size.
+     *
+     * @return the page size
+     */
+    public Integer getPageSize() {
+        return pageSize;
+    }
+
+    /**
+     * Sets page size.
+     *
+     * @param pageSize the page size
+     */
+    public void setPageSize(Integer pageSize) {
+        this.pageSize = pageSize;
+    }
+}

+ 124 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/base/RestResponse.java

@@ -0,0 +1,124 @@
+package vip.xiaonuo.exam.base;
+
+/**
+ * @version 3.3.0
+ * @description: The type Rest response.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/5/25 10:45
+ */
+public class RestResponse<T> {
+    private int code;
+    private String message;
+    private T response;
+
+    /**
+     * Instantiates a new Rest response.
+     *
+     * @param code    the code
+     * @param message the message
+     */
+    public RestResponse(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    /**
+     * Instantiates a new Rest response.
+     *
+     * @param code     the code
+     * @param message  the message
+     * @param response the response
+     */
+    public RestResponse(int code, String message, T response) {
+        this.code = code;
+        this.message = message;
+        this.response = response;
+    }
+
+    /**
+     * Fail rest response.
+     *
+     * @param code the code
+     * @param msg  the msg
+     * @return the rest response
+     */
+    public static RestResponse fail(Integer code, String msg) {
+        return new RestResponse<>(code, msg);
+    }
+
+    /**
+     * Ok rest response.
+     *
+     * @return the rest response
+     */
+    public static RestResponse ok() {
+        SystemCode systemCode = SystemCode.OK;
+        return new RestResponse<>(systemCode.getCode(), systemCode.getMessage());
+    }
+
+    /**
+     * Ok rest response.
+     *
+     * @param <F>      the type parameter
+     * @param response the response
+     * @return the rest response
+     */
+    public static <F> RestResponse<F> ok(F response) {
+        SystemCode systemCode = SystemCode.OK;
+        return new RestResponse<>(systemCode.getCode(), systemCode.getMessage(), response);
+    }
+
+    /**
+     * Gets code.
+     *
+     * @return the code
+     */
+    public int getCode() {
+        return code;
+    }
+
+    /**
+     * Sets code.
+     *
+     * @param code the code
+     */
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    /**
+     * Gets message.
+     *
+     * @return the message
+     */
+    public String getMessage() {
+        return message;
+    }
+
+    /**
+     * Sets message.
+     *
+     * @param message the message
+     */
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    /**
+     * Gets response.
+     *
+     * @return the response
+     */
+    public T getResponse() {
+        return response;
+    }
+
+    /**
+     * Sets response.
+     *
+     * @param response the response
+     */
+    public void setResponse(T response) {
+        this.response = response;
+    }
+}

+ 71 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/base/SystemCode.java

@@ -0,0 +1,71 @@
+package vip.xiaonuo.exam.base;
+
+/**
+ * @version 3.3.0
+ * @description: The enum System code.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/5/25 10:45
+ */
+public enum SystemCode {
+    /**
+     * OK
+     */
+    OK(1, "成功"),
+    /**
+     * AccessTokenError
+     */
+    AccessTokenError(400, "用户登录令牌失效"),
+    /**
+     * UNAUTHORIZED
+     */
+    UNAUTHORIZED(401, "用户未登录"),
+    /**
+     * UNAUTHORIZED
+     */
+    AuthError(402, "用户名或密码错误"),
+    /**
+     * InnerError
+     */
+    InnerError(500, "系统内部错误"),
+    /**
+     * ParameterValidError
+     */
+    ParameterValidError(501, "参数验证错误"),
+
+    /**
+     * AccessDenied
+     */
+    AccessDenied(502, "用户没有权限访问");
+
+    /**
+     * The Code.
+     */
+    int code;
+    /**
+     * The Message.
+     */
+    String message;
+
+    SystemCode(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    /**
+     * Gets code.
+     *
+     * @return the code
+     */
+    public int getCode() {
+        return code;
+    }
+
+    /**
+     * Gets message.
+     *
+     * @return the message
+     */
+    public String getMessage() {
+        return message;
+    }
+}

+ 49 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/application/ApplicationContextProvider.java

@@ -0,0 +1,49 @@
+package vip.xiaonuo.exam.configuration.application;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * @version 3.5.0
+ * @description: The type Application context provider.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@Component
+public class ApplicationContextProvider implements ApplicationContextAware {
+    private static ApplicationContext context;
+
+    private ApplicationContextProvider() {
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        context = applicationContext;
+    }
+
+    /**
+     * Gets bean.
+     *
+     * @param <T>    the type parameter
+     * @param aClass the a class
+     * @return the bean
+     */
+    public static <T> T getBean(Class<T> aClass) {
+        return context.getBean(aClass);
+    }
+
+    /**
+     * Gets bean.
+     *
+     * @param <T>  the type parameter
+     * @param name the name
+     * @return the bean
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getBean(String name) {
+        return (T) context.getBean(name);
+    }
+}
+

+ 28 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/property/CookieConfig.java

@@ -0,0 +1,28 @@
+package vip.xiaonuo.exam.configuration.property;
+
+/**
+ * @version 3.5.0
+ * @description: The type Cookie config.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+public class CookieConfig {
+
+    /**
+     * Gets name.
+     *
+     * @return the name
+     */
+    public static String getName() {
+        return "xzs";
+    }
+
+    /**
+     * Gets interval.
+     *
+     * @return the interval
+     */
+    public static Integer getInterval() {
+        return 30 * 24 * 60 * 60;
+    }
+}

+ 50 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/property/PasswordKeyConfig.java

@@ -0,0 +1,50 @@
+package vip.xiaonuo.exam.configuration.property;
+
+/**
+ * @version 3.5.0
+ * @description: The type Password key config.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+public class PasswordKeyConfig {
+    private String publicKey;
+
+    private String privateKey;
+
+    /**
+     * Gets public key.
+     *
+     * @return the public key
+     */
+    public String getPublicKey() {
+        return publicKey;
+    }
+
+    /**
+     * Sets public key.
+     *
+     * @param publicKey the public key
+     */
+    public void setPublicKey(String publicKey) {
+        this.publicKey = publicKey;
+    }
+
+    /**
+     * Gets private key.
+     *
+     * @return the private key
+     */
+    public String getPrivateKey() {
+        return privateKey;
+    }
+
+    /**
+     * Sets private key.
+     *
+     * @param privateKey the private key
+     */
+    public void setPrivateKey(String privateKey) {
+        this.privateKey = privateKey;
+    }
+
+}

+ 92 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/property/QnConfig.java

@@ -0,0 +1,92 @@
+package vip.xiaonuo.exam.configuration.property;
+
+
+import java.time.Duration;
+import java.util.List;
+
+/**
+ * @version 3.5.0
+ * @description: The type Qn config.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+public class QnConfig {
+
+    private String url;
+    private String bucket;
+    private String accessKey;
+    private String secretKey;
+
+    /**
+     * Gets url.
+     *
+     * @return the url
+     */
+    public String getUrl() {
+        return url;
+    }
+
+    /**
+     * Sets url.
+     *
+     * @param url the url
+     */
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    /**
+     * Gets bucket.
+     *
+     * @return the bucket
+     */
+    public String getBucket() {
+        return bucket;
+    }
+
+    /**
+     * Sets bucket.
+     *
+     * @param bucket the bucket
+     */
+    public void setBucket(String bucket) {
+        this.bucket = bucket;
+    }
+
+    /**
+     * Gets access key.
+     *
+     * @return the access key
+     */
+    public String getAccessKey() {
+        return accessKey;
+    }
+
+    /**
+     * Sets access key.
+     *
+     * @param accessKey the access key
+     */
+    public void setAccessKey(String accessKey) {
+        this.accessKey = accessKey;
+    }
+
+    /**
+     * Gets secret key.
+     *
+     * @return the secret key
+     */
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    /**
+     * Sets secret key.
+     *
+     * @param secretKey the secret key
+     */
+    public void setSecretKey(String secretKey) {
+        this.secretKey = secretKey;
+    }
+
+}

+ 94 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/property/SystemConfig.java

@@ -0,0 +1,94 @@
+package vip.xiaonuo.exam.configuration.property;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.List;
+
+
+/**
+ * @version 3.5.0
+ * @description: The type System config.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@ConfigurationProperties(prefix = "system")
+public class SystemConfig {
+
+    private PasswordKeyConfig pwdKey;
+    private List<String> securityIgnoreUrls;
+    private WxConfig wx;
+    private QnConfig qn;
+
+    /**
+     * Gets pwd key.
+     *
+     * @return the pwd key
+     */
+    public PasswordKeyConfig getPwdKey() {
+        return pwdKey;
+    }
+
+    /**
+     * Sets pwd key.
+     *
+     * @param pwdKey the pwd key
+     */
+    public void setPwdKey(PasswordKeyConfig pwdKey) {
+        this.pwdKey = pwdKey;
+    }
+
+    /**
+     * Gets security ignore urls.
+     *
+     * @return the security ignore urls
+     */
+    public List<String> getSecurityIgnoreUrls() {
+        return securityIgnoreUrls;
+    }
+
+    /**
+     * Sets security ignore urls.
+     *
+     * @param securityIgnoreUrls the security ignore urls
+     */
+    public void setSecurityIgnoreUrls(List<String> securityIgnoreUrls) {
+        this.securityIgnoreUrls = securityIgnoreUrls;
+    }
+
+    /**
+     * Gets wx.
+     *
+     * @return the wx
+     */
+    public WxConfig getWx() {
+        return wx;
+    }
+
+    /**
+     * Sets wx.
+     *
+     * @param wx the wx
+     */
+    public void setWx(WxConfig wx) {
+        this.wx = wx;
+    }
+
+    /**
+     * Gets qn.
+     *
+     * @return the qn
+     */
+    public QnConfig getQn() {
+        return qn;
+    }
+
+    /**
+     * Sets qn.
+     *
+     * @param qn the qn
+     */
+    public void setQn(QnConfig qn) {
+        this.qn = qn;
+    }
+
+}

+ 93 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/property/WxConfig.java

@@ -0,0 +1,93 @@
+package vip.xiaonuo.exam.configuration.property;
+
+
+import java.time.Duration;
+import java.util.List;
+
+/**
+ * @version 3.5.0
+ * @description: The type Wx config.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+public class WxConfig {
+
+
+    private String appid;
+    private String secret;
+    private Duration tokenToLive;
+    private List<String> securityIgnoreUrls;
+
+    /**
+     * Gets appid.
+     *
+     * @return the appid
+     */
+    public String getAppid() {
+        return appid;
+    }
+
+    /**
+     * Sets appid.
+     *
+     * @param appid the appid
+     */
+    public void setAppid(String appid) {
+        this.appid = appid;
+    }
+
+    /**
+     * Gets secret.
+     *
+     * @return the secret
+     */
+    public String getSecret() {
+        return secret;
+    }
+
+    /**
+     * Sets secret.
+     *
+     * @param secret the secret
+     */
+    public void setSecret(String secret) {
+        this.secret = secret;
+    }
+
+    /**
+     * Gets token to live.
+     *
+     * @return the token to live
+     */
+    public Duration getTokenToLive() {
+        return tokenToLive;
+    }
+
+    /**
+     * Sets token to live.
+     *
+     * @param tokenToLive the token to live
+     */
+    public void setTokenToLive(Duration tokenToLive) {
+        this.tokenToLive = tokenToLive;
+    }
+
+    /**
+     * Gets security ignore urls.
+     *
+     * @return the security ignore urls
+     */
+    public List<String> getSecurityIgnoreUrls() {
+        return securityIgnoreUrls;
+    }
+
+    /**
+     * Sets security ignore urls.
+     *
+     * @param securityIgnoreUrls the security ignore urls
+     */
+    public void setSecurityIgnoreUrls(List<String> securityIgnoreUrls) {
+        this.securityIgnoreUrls = securityIgnoreUrls;
+    }
+
+}

+ 73 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/exception/ExceptionHandle.java

@@ -0,0 +1,73 @@
+package vip.xiaonuo.exam.configuration.spring.exception;
+
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.base.SystemCode;
+import vip.xiaonuo.exam.utility.ErrorUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import java.util.stream.Collectors;
+
+/**
+ * @version 3.5.0
+ * @description: The type Exception handle.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@ControllerAdvice
+public class ExceptionHandle {
+    private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);
+
+    /**
+     * Handler rest response.
+     *
+     * @param e the e
+     * @return the rest response
+     */
+    @ExceptionHandler(Exception.class)
+    @ResponseBody
+    public RestResponse handler(Exception e) {
+        logger.error(e.getMessage(), e);
+        return new RestResponse<>(SystemCode.InnerError.getCode(), SystemCode.InnerError.getMessage());
+    }
+
+    /**
+     * Handler rest response.
+     *
+     * @param e the e
+     * @return the rest response
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    @ResponseBody
+    public RestResponse handler(MethodArgumentNotValidException e) {
+        String errorMsg = e.getBindingResult().getAllErrors().stream().map(file -> {
+            FieldError fieldError = (FieldError) file;
+            return ErrorUtil.parameterErrorFormat(fieldError.getField(), fieldError.getDefaultMessage());
+        }).collect(Collectors.joining());
+        return new RestResponse<>(SystemCode.ParameterValidError.getCode(), errorMsg);
+    }
+
+    /**
+     * Handler rest response.
+     *
+     * @param e the e
+     * @return the rest response
+     */
+    @ExceptionHandler(BindException.class)
+    @ResponseBody
+    public RestResponse handler(BindException e) {
+        String errorMsg = e.getBindingResult().getAllErrors().stream().map(file -> {
+            FieldError fieldError = (FieldError) file;
+            return ErrorUtil.parameterErrorFormat(fieldError.getField(), fieldError.getDefaultMessage());
+        }).collect(Collectors.joining());
+        return new RestResponse<>(SystemCode.ParameterValidError.getCode(), errorMsg);
+    }
+
+
+}

+ 70 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/mvc/WebMvcConfiguration.java

@@ -0,0 +1,70 @@
+package vip.xiaonuo.exam.configuration.spring.mvc;
+
+import vip.xiaonuo.exam.configuration.property.SystemConfig;
+import vip.xiaonuo.exam.configuration.spring.wx.TokenHandlerInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.*;
+
+import java.util.List;
+
+
+/**
+ * @version 3.5.0
+ * @description: The type Web mvc configuration.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@Configuration
+public class WebMvcConfiguration extends WebMvcConfigurationSupport {
+
+    private final TokenHandlerInterceptor tokenHandlerInterceptor;
+    private final SystemConfig systemConfig;
+
+    /**
+     * Instantiates a new Web mvc configuration.
+     *
+     * @param tokenHandlerInterceptor the token handler interceptor
+     * @param systemConfig            the system config
+     */
+    @Autowired
+    public WebMvcConfiguration(TokenHandlerInterceptor tokenHandlerInterceptor, SystemConfig systemConfig) {
+        this.tokenHandlerInterceptor = tokenHandlerInterceptor;
+        this.systemConfig = systemConfig;
+    }
+
+    @Override
+    public void addViewControllers(ViewControllerRegistry registry) {
+        registry.addRedirectViewController("/", "/student/index.html");
+        registry.addRedirectViewController("/student", "/student/index.html");
+        registry.addRedirectViewController("/admin", "/admin/index.html");
+    }
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("/**")
+                .addResourceLocations("classpath:/static/")
+                .setCachePeriod(31556926);
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        List<String> securityIgnoreUrls = systemConfig.getWx().getSecurityIgnoreUrls();
+        String[] ignores = new String[securityIgnoreUrls.size()];
+        registry.addInterceptor(tokenHandlerInterceptor)
+                .addPathPatterns("/api/wx/**")
+                .excludePathPatterns(securityIgnoreUrls.toArray(ignores));
+        super.addInterceptors(registry);
+    }
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowCredentials(true)
+                .allowedMethods("*")
+                .allowedOrigins("*")
+                .allowedHeaders("*");
+        super.addCorsMappings(registry);
+    }
+
+}

+ 68 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/AuthenticationBean.java

@@ -0,0 +1,68 @@
+package vip.xiaonuo.exam.configuration.spring.security;
+
+/**
+ * @version 3.5.0
+ * @description: The type Authentication bean.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+public class AuthenticationBean {
+    private String userName;
+    private String password;
+    private boolean remember;
+
+    /**
+     * Gets user name.
+     *
+     * @return the user name
+     */
+    public String getUserName() {
+        return userName;
+    }
+
+    /**
+     * Sets user name.
+     *
+     * @param userName the user name
+     */
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    /**
+     * Gets password.
+     *
+     * @return the password
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * Sets password.
+     *
+     * @param password the password
+     */
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    /**
+     * Is remember boolean.
+     *
+     * @return the boolean
+     */
+    public boolean isRemember() {
+        return remember;
+    }
+
+    /**
+     * Sets remember.
+     *
+     * @param remember the remember
+     */
+    public void setRemember(boolean remember) {
+        this.remember = remember;
+    }
+
+}

+ 35 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/LoginAuthenticationEntryPoint.java

@@ -0,0 +1,35 @@
+package vip.xiaonuo.exam.configuration.spring.security;
+
+import vip.xiaonuo.exam.base.SystemCode;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @version 3.5.0
+ * @description: 未登录
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@Component
+public final class LoginAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
+
+    /**
+     * Instantiates a new Login authentication entry point.
+     */
+    public LoginAuthenticationEntryPoint() {
+        super("/api/user/login");
+    }
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response,
+                         AuthenticationException authException) throws IOException, ServletException {
+        RestUtil.response(response, SystemCode.UNAUTHORIZED);
+    }
+
+}

+ 26 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestAccessDeniedHandler.java

@@ -0,0 +1,26 @@
+package vip.xiaonuo.exam.configuration.spring.security;
+
+import vip.xiaonuo.exam.base.SystemCode;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+/**
+ * @version 3.5.0
+ * @description: 没权限
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@Component
+public class RestAccessDeniedHandler implements AccessDeniedHandler {
+    @Override
+    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
+        RestUtil.response(httpServletResponse, SystemCode.AccessDenied);
+    }
+}

+ 27 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestAuthenticationFailureHandler.java

@@ -0,0 +1,27 @@
+package vip.xiaonuo.exam.configuration.spring.security;
+
+import vip.xiaonuo.exam.base.SystemCode;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+/**
+ * @version 3.5.0
+ * @description: 账号验证异常
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@Component
+public class RestAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
+
+    @Override
+    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
+        RestUtil.response(response, SystemCode.AuthError.getCode(), exception.getMessage());
+    }
+}

+ 83 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestAuthenticationProvider.java

@@ -0,0 +1,83 @@
+package vip.xiaonuo.exam.configuration.spring.security;
+
+
+import vip.xiaonuo.exam.context.WebContext;
+import vip.xiaonuo.exam.domain.enums.RoleEnum;
+import vip.xiaonuo.exam.domain.enums.UserStatusEnum;
+import vip.xiaonuo.exam.service.AuthenticationService;
+import vip.xiaonuo.exam.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.LockedException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+
+
+/**
+ * @version 3.5.0
+ * @description: 登录用户名密码验证
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@Component
+public class RestAuthenticationProvider implements AuthenticationProvider {
+
+    private final AuthenticationService authenticationService;
+    private final UserService userService;
+    private final WebContext webContext;
+
+    /**
+     * Instantiates a new Rest authentication provider.
+     *
+     * @param authenticationService the authentication service
+     * @param userService           the user service
+     * @param webContext            the web context
+     */
+    @Autowired
+    public RestAuthenticationProvider(AuthenticationService authenticationService, UserService userService, WebContext webContext) {
+        this.authenticationService = authenticationService;
+        this.userService = userService;
+        this.webContext = webContext;
+    }
+
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+        String username = authentication.getName();
+        String password = (String) authentication.getCredentials();
+
+        vip.xiaonuo.exam.domain.User user = userService.getUserByUserName(username);
+        if (user == null) {
+            throw new UsernameNotFoundException("用户名或密码错误");
+        }
+
+        boolean result = authenticationService.authUser(user, username, password);
+        if (!result) {
+            throw new BadCredentialsException("用户名或密码错误");
+        }
+
+        UserStatusEnum userStatusEnum = UserStatusEnum.fromCode(user.getStatus());
+        if (UserStatusEnum.Disable == userStatusEnum) {
+            throw new LockedException("用户被禁用");
+        }
+
+        ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>();
+        grantedAuthorities.add(new SimpleGrantedAuthority(RoleEnum.fromCode(user.getRole()).getRoleName()));
+
+        User authUser = new User(user.getUserName(), user.getPassword(), grantedAuthorities);
+        return new UsernamePasswordAuthenticationToken(authUser, authUser.getPassword(), authUser.getAuthorities());
+    }
+
+    @Override
+    public boolean supports(Class<?> aClass) {
+        return true;
+    }
+}

+ 64 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestAuthenticationSuccessHandler.java

@@ -0,0 +1,64 @@
+package vip.xiaonuo.exam.configuration.spring.security;
+
+import vip.xiaonuo.exam.base.SystemCode;
+import vip.xiaonuo.exam.domain.UserEventLog;
+import vip.xiaonuo.exam.event.UserEvent;
+import vip.xiaonuo.exam.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Date;
+
+
+/**
+ * @version 3.5.0
+ * @description: 登录成功返回
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@Component
+public class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
+
+    private final ApplicationEventPublisher eventPublisher;
+    private final UserService userService;
+
+    /**
+     * Instantiates a new Rest authentication success handler.
+     *
+     * @param eventPublisher the event publisher
+     * @param userService    the user service
+     */
+    @Autowired
+    public RestAuthenticationSuccessHandler(ApplicationEventPublisher eventPublisher, UserService userService) {
+        this.eventPublisher = eventPublisher;
+        this.userService = userService;
+    }
+
+    @Override
+    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
+        Object object = authentication.getPrincipal();
+        if (null != object) {
+            User springUser = (User) object;
+            vip.xiaonuo.exam.domain.User user = userService.getUserByUserName(springUser.getUsername());
+            if (null != user) {
+                UserEventLog userEventLog = new UserEventLog(user.getId(), user.getUserName(), user.getRealName(), new Date());
+                userEventLog.setContent(user.getUserName() + " 登录了学之思开源考试系统");
+                eventPublisher.publishEvent(new UserEvent(userEventLog));
+                vip.xiaonuo.exam.domain.User newUser = new vip.xiaonuo.exam.domain.User();
+                newUser.setUserName(user.getUserName());
+                newUser.setImagePath(user.getImagePath());
+                RestUtil.response(response, SystemCode.OK.getCode(), SystemCode.OK.getMessage(), newUser);
+            }
+        } else {
+            RestUtil.response(response, SystemCode.UNAUTHORIZED.getCode(), SystemCode.UNAUTHORIZED.getMessage());
+        }
+    }
+}

+ 52 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestDetailsServiceImpl.java

@@ -0,0 +1,52 @@
+package vip.xiaonuo.exam.configuration.spring.security;
+
+import vip.xiaonuo.exam.domain.enums.RoleEnum;
+import vip.xiaonuo.exam.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+
+
+/**
+ * @version 3.5.0
+ * @description: 验证通过之后,第二、三...请求,会调用此类
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@Component
+public class RestDetailsServiceImpl implements UserDetailsService {
+
+    private final UserService userService;
+
+    /**
+     * Instantiates a new Rest details service.
+     *
+     * @param userService the user service
+     */
+    @Autowired
+    public RestDetailsServiceImpl(UserService userService) {
+        this.userService = userService;
+    }
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+
+        vip.xiaonuo.exam.domain.User user = userService.getUserByUserName(username);
+
+        if (user == null) {
+            throw new UsernameNotFoundException("Username  not found.");
+        }
+
+        ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>();
+        grantedAuthorities.add(new SimpleGrantedAuthority(RoleEnum.fromCode(user.getRole()).getRoleName()));
+
+        return new User(user.getUserName(), user.getPassword(), grantedAuthorities);
+    }
+}

+ 67 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestLoginAuthenticationFilter.java

@@ -0,0 +1,67 @@
+package vip.xiaonuo.exam.configuration.spring.security;
+
+import vip.xiaonuo.exam.configuration.property.CookieConfig;
+
+import vip.xiaonuo.exam.utility.JsonUtil;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * @version 3.5.0
+ * @description: 登录参数序列化
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+public class RestLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
+    private final org.slf4j.Logger logger = LoggerFactory.getLogger(RestLoginAuthenticationFilter.class);
+
+    /**
+     * Instantiates a new Rest login authentication filter.
+     */
+    public RestLoginAuthenticationFilter() {
+        super(new AntPathRequestMatcher("/api/user/login", "POST"));
+    }
+
+    @Override
+    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
+        UsernamePasswordAuthenticationToken authRequest;
+        try (InputStream is = request.getInputStream()) {
+            AuthenticationBean authenticationBean = JsonUtil.toJsonObject(is, AuthenticationBean.class);
+            request.setAttribute(TokenBasedRememberMeServices.DEFAULT_PARAMETER, authenticationBean.isRemember());
+            authRequest = new UsernamePasswordAuthenticationToken(authenticationBean.getUserName(), authenticationBean.getPassword());
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+            authRequest = new UsernamePasswordAuthenticationToken("", "");
+        }
+        setDetails(request, authRequest);
+        return this.getAuthenticationManager().authenticate(authRequest);
+
+    }
+
+    /**
+     * Sets user details service.
+     *
+     * @param userDetailsService the user details service
+     */
+    void setUserDetailsService(UserDetailsService userDetailsService) {
+        RestTokenBasedRememberMeServices tokenBasedRememberMeServices = new RestTokenBasedRememberMeServices(CookieConfig.getName(), userDetailsService);
+        tokenBasedRememberMeServices.setTokenValiditySeconds(CookieConfig.getInterval());
+        setRememberMeServices(tokenBasedRememberMeServices);
+    }
+
+    private void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
+        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
+    }
+}

+ 54 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestLogoutSuccessHandler.java

@@ -0,0 +1,54 @@
+package vip.xiaonuo.exam.configuration.spring.security;
+
+import vip.xiaonuo.exam.base.SystemCode;
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.domain.UserEventLog;
+import vip.xiaonuo.exam.event.UserEvent;
+import vip.xiaonuo.exam.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Date;
+
+
+/**
+ * @version 3.5.0
+ * @description: 用户登出
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@Component
+public class RestLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
+
+    private final ApplicationEventPublisher eventPublisher;
+    private final UserService userService;
+
+    /**
+     * Instantiates a new Rest logout success handler.
+     *
+     * @param eventPublisher the event publisher
+     * @param userService    the user service
+     */
+    @Autowired
+    public RestLogoutSuccessHandler(ApplicationEventPublisher eventPublisher, UserService userService) {
+        this.eventPublisher = eventPublisher;
+        this.userService = userService;
+    }
+
+    @Override
+    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
+        org.springframework.security.core.userdetails.User springUser = (org.springframework.security.core.userdetails.User) authentication.getPrincipal();
+        if (null != springUser) {
+            User user = userService.getUserByUserName(springUser.getUsername());
+            UserEventLog userEventLog = new UserEventLog(user.getId(), user.getUserName(), user.getRealName(), new Date());
+            userEventLog.setContent(user.getUserName() + " 登出了学之思开源考试系统");
+            eventPublisher.publishEvent(new UserEvent(userEventLog));
+        }
+        RestUtil.response(response, SystemCode.OK);
+    }
+}

+ 31 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestTokenBasedRememberMeServices.java

@@ -0,0 +1,31 @@
+package vip.xiaonuo.exam.configuration.spring.security;
+
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * @version 3.5.0
+ * @description: 记住我,Cookie
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+public class RestTokenBasedRememberMeServices extends TokenBasedRememberMeServices {
+    /**
+     * Instantiates a new Rest token based remember me services.
+     *
+     * @param key                the key
+     * @param userDetailsService the user details service
+     */
+    public RestTokenBasedRememberMeServices(String key, UserDetailsService userDetailsService) {
+        super(key, userDetailsService);
+    }
+
+    @Override
+    protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
+        return (boolean) request.getAttribute(DEFAULT_PARAMETER);
+    }
+
+}

+ 63 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/RestUtil.java

@@ -0,0 +1,63 @@
+package vip.xiaonuo.exam.configuration.spring.security;
+
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.base.SystemCode;
+import vip.xiaonuo.exam.utility.JsonUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+/**
+ * @version 3.5.0
+ * @description: The type Rest util.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+public class RestUtil {
+    private static final Logger logger = LoggerFactory.getLogger(RestUtil.class);
+
+
+    /**
+     * Response.
+     *
+     * @param response   the response
+     * @param systemCode the system code
+     */
+    public static void response(HttpServletResponse response, SystemCode systemCode) {
+        response(response, systemCode.getCode(), systemCode.getMessage());
+    }
+
+    /**
+     * Response.
+     *
+     * @param response   the response
+     * @param systemCode the system code
+     * @param msg        the msg
+     */
+    public static void response(HttpServletResponse response, int systemCode, String msg) {
+        response(response, systemCode, msg, null);
+    }
+
+
+    /**
+     * Response.
+     *
+     * @param response   the response
+     * @param systemCode the system code
+     * @param msg        the msg
+     * @param content    the content
+     */
+    public static void response(HttpServletResponse response, int systemCode, String msg, Object content) {
+        try {
+            RestResponse res = new RestResponse<>(systemCode, msg, content);
+            String resStr = JsonUtil.toJsonStr(res);
+            response.setContentType("application/json;charset=utf-8");
+            response.getWriter().write(resStr);
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+        }
+    }
+}

+ 136 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/security/SecurityConfigurer.java

@@ -0,0 +1,136 @@
+package vip.xiaonuo.exam.configuration.spring.security;
+
+import vip.xiaonuo.exam.configuration.property.CookieConfig;
+import vip.xiaonuo.exam.configuration.property.SystemConfig;
+import vip.xiaonuo.exam.domain.enums.RoleEnum;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * @version 3.5.0
+ * @description: The type Security configurer.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@Configuration
+@EnableWebSecurity
+public class SecurityConfigurer {
+
+    /**
+     * The type Form login web security configurer adapter.
+     */
+    @Configuration
+    public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
+
+        private final SystemConfig systemConfig;
+        private final LoginAuthenticationEntryPoint restAuthenticationEntryPoint;
+        private final RestAuthenticationProvider restAuthenticationProvider;
+        private final RestDetailsServiceImpl formDetailsService;
+        private final RestAuthenticationSuccessHandler restAuthenticationSuccessHandler;
+        private final RestAuthenticationFailureHandler restAuthenticationFailureHandler;
+        private final RestLogoutSuccessHandler restLogoutSuccessHandler;
+        private final RestAccessDeniedHandler restAccessDeniedHandler;
+
+        /**
+         * Instantiates a new Form login web security configurer adapter.
+         *
+         * @param systemConfig                     the system config
+         * @param restAuthenticationEntryPoint     the rest authentication entry point
+         * @param restAuthenticationProvider       the rest authentication provider
+         * @param formDetailsService               the form details service
+         * @param restAuthenticationSuccessHandler the rest authentication success handler
+         * @param restAuthenticationFailureHandler the rest authentication failure handler
+         * @param restLogoutSuccessHandler         the rest logout success handler
+         * @param restAccessDeniedHandler          the rest access denied handler
+         */
+        @Autowired
+        public FormLoginWebSecurityConfigurerAdapter(SystemConfig systemConfig, LoginAuthenticationEntryPoint restAuthenticationEntryPoint, RestAuthenticationProvider restAuthenticationProvider, RestDetailsServiceImpl formDetailsService, RestAuthenticationSuccessHandler restAuthenticationSuccessHandler, RestAuthenticationFailureHandler restAuthenticationFailureHandler, RestLogoutSuccessHandler restLogoutSuccessHandler, RestAccessDeniedHandler restAccessDeniedHandler) {
+            this.systemConfig = systemConfig;
+            this.restAuthenticationEntryPoint = restAuthenticationEntryPoint;
+            this.restAuthenticationProvider = restAuthenticationProvider;
+            this.formDetailsService = formDetailsService;
+            this.restAuthenticationSuccessHandler = restAuthenticationSuccessHandler;
+            this.restAuthenticationFailureHandler = restAuthenticationFailureHandler;
+            this.restLogoutSuccessHandler = restLogoutSuccessHandler;
+            this.restAccessDeniedHandler = restAccessDeniedHandler;
+        }
+
+        /**
+         * @param http http
+         * @throws Exception exception
+         *                   csrf is the from submit get method
+         */
+        @Override
+        protected void configure(HttpSecurity http) throws Exception {
+            http.headers().frameOptions().disable();
+
+            List<String> securityIgnoreUrls = systemConfig.getSecurityIgnoreUrls();
+            String[] ignores = new String[securityIgnoreUrls.size()];
+            http
+                    .addFilterAt(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
+                    .exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint)
+                    .and().authenticationProvider(restAuthenticationProvider)
+                    .authorizeRequests()
+                    .antMatchers(securityIgnoreUrls.toArray(ignores)).permitAll()
+                    .antMatchers("/api/admin/**").hasRole(RoleEnum.ADMIN.getName())
+                    .antMatchers("/api/student/**").hasRole(RoleEnum.STUDENT.getName())
+                    .anyRequest().permitAll()
+                    .and().exceptionHandling().accessDeniedHandler(restAccessDeniedHandler)
+                    .and().formLogin().successHandler(restAuthenticationSuccessHandler).failureHandler(restAuthenticationFailureHandler)
+                    .and().logout().logoutUrl("/api/user/logout").logoutSuccessHandler(restLogoutSuccessHandler).invalidateHttpSession(true)
+                    .and().rememberMe().key(CookieConfig.getName()).tokenValiditySeconds(CookieConfig.getInterval()).userDetailsService(formDetailsService)
+                    .and().csrf().disable()
+                    .cors();
+        }
+
+
+        /**
+         * Cors configuration source cors configuration source.
+         *
+         * @return the cors configuration source
+         */
+        @Bean
+        public CorsConfigurationSource corsConfigurationSource() {
+            final CorsConfiguration configuration = new CorsConfiguration();
+            configuration.setMaxAge(3600L);
+            configuration.setAllowedOrigins(Collections.singletonList("*"));
+            configuration.setAllowedMethods(Collections.singletonList("*"));
+            configuration.setAllowCredentials(true);
+            configuration.setAllowedHeaders(Collections.singletonList("*"));
+            final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+            source.registerCorsConfiguration("/api/**", configuration);
+            return source;
+        }
+
+
+        /**
+         * Authentication filter rest login authentication filter.
+         *
+         * @return the rest login authentication filter
+         * @throws Exception the exception
+         */
+        @Bean
+        public RestLoginAuthenticationFilter authenticationFilter() throws Exception {
+            RestLoginAuthenticationFilter authenticationFilter = new RestLoginAuthenticationFilter();
+            authenticationFilter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler);
+            authenticationFilter.setAuthenticationFailureHandler(restAuthenticationFailureHandler);
+            authenticationFilter.setAuthenticationManager(authenticationManagerBean());
+            authenticationFilter.setUserDetailsService(formDetailsService);
+            return authenticationFilter;
+        }
+
+
+    }
+}

+ 68 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/configuration/spring/wx/TokenHandlerInterceptor.java

@@ -0,0 +1,68 @@
+package vip.xiaonuo.exam.configuration.spring.wx;
+
+import vip.xiaonuo.exam.base.SystemCode;
+import vip.xiaonuo.exam.configuration.spring.security.RestUtil;
+import vip.xiaonuo.exam.context.WxContext;
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.domain.UserToken;
+import vip.xiaonuo.exam.service.UserService;
+import vip.xiaonuo.exam.service.UserTokenService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Date;
+
+@Component
+public class TokenHandlerInterceptor implements HandlerInterceptor {
+
+    private final UserTokenService userTokenService;
+    private final UserService userService;
+    private final WxContext wxContext;
+
+    @Autowired
+    public TokenHandlerInterceptor(UserTokenService userTokenService, UserService userService, WxContext wxContext) {
+        this.userTokenService = userTokenService;
+        this.userService = userService;
+        this.wxContext = wxContext;
+    }
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        String token = request.getHeader("token");
+        if (StringUtils.isEmpty(token)) {
+            RestUtil.response(response, SystemCode.UNAUTHORIZED);
+            return false;
+        }
+
+        if (StringUtils.isBlank(token)) {
+            RestUtil.response(response, SystemCode.UNAUTHORIZED);
+            return false;
+        }
+
+        if (token.length() != 36) {
+            RestUtil.response(response, SystemCode.UNAUTHORIZED);
+            return false;
+        }
+
+        UserToken userToken = userTokenService.getToken(token);
+        if (null == userToken) {
+            RestUtil.response(response, SystemCode.UNAUTHORIZED);
+            return false;
+        }
+
+        Date now = new Date();
+        User user = userService.getUserByUserName(userToken.getUserName());
+        if (now.before(userToken.getEndTime())) {
+            wxContext.setContext(user,userToken);
+            return true;
+        } else {   //refresh token
+            UserToken refreshToken = userTokenService.insertUserToken(user);
+            RestUtil.response(response, SystemCode.AccessTokenError.getCode(), SystemCode.AccessTokenError.getMessage(), refreshToken.getToken());
+            return false;
+        }
+    }
+}

+ 63 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/context/WebContext.java

@@ -0,0 +1,63 @@
+package vip.xiaonuo.exam.context;
+
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+
+/**
+ * @version 3.3.0
+ * @description: The enum System code.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/5/25 10:45
+ */
+@Component
+public class WebContext {
+    private static final String USER_ATTRIBUTES = "USER_ATTRIBUTES";
+    private final UserService userService;
+
+    /**
+     * Instantiates a new Web context.
+     *
+     * @param userService the user service
+     */
+    @Autowired
+    public WebContext(UserService userService) {
+        this.userService = userService;
+    }
+
+
+    /**
+     * Sets current user.
+     *
+     * @param user the user
+     */
+    public void setCurrentUser(User user) {
+        RequestContextHolder.currentRequestAttributes().setAttribute(USER_ATTRIBUTES, user, RequestAttributes.SCOPE_REQUEST);
+    }
+
+    /**
+     * Gets current user.
+     *
+     * @return the current user
+     */
+    public User getCurrentUser() {
+        User user = (User) RequestContextHolder.currentRequestAttributes().getAttribute(USER_ATTRIBUTES, RequestAttributes.SCOPE_REQUEST);
+        if (null != user) {
+            return user;
+        } else {
+            org.springframework.security.core.userdetails.User springUser = (org.springframework.security.core.userdetails.User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+            if (null == springUser) {
+                return null;
+            }
+            user = userService.getUserByUserName(springUser.getUsername());
+            if (null != user) {
+                setCurrentUser(user);
+            }
+            return user;
+        }
+    }
+}

+ 50 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/context/WxContext.java

@@ -0,0 +1,50 @@
+package vip.xiaonuo.exam.context;
+
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.domain.UserToken;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+
+/**
+ * @version 3.3.0
+ * @description: The enum System code.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/5/25 10:45
+ */
+@Component
+public class WxContext {
+
+    private static final String USER_ATTRIBUTES = "USER_ATTRIBUTES";
+    private static final String USER_TOKEN_ATTRIBUTES = "USER_TOKEN_ATTRIBUTES";
+
+
+    /**
+     * Sets context.
+     *
+     * @param user      the user
+     * @param userToken the user token
+     */
+    public void setContext(User user, UserToken userToken) {
+        RequestContextHolder.currentRequestAttributes().setAttribute(USER_ATTRIBUTES, user, RequestAttributes.SCOPE_REQUEST);
+        RequestContextHolder.currentRequestAttributes().setAttribute(USER_TOKEN_ATTRIBUTES, userToken, RequestAttributes.SCOPE_REQUEST);
+    }
+
+    /**
+     * Gets current user.
+     *
+     * @return the current user
+     */
+    public User getCurrentUser() {
+        return (User) RequestContextHolder.currentRequestAttributes().getAttribute(USER_ATTRIBUTES, RequestAttributes.SCOPE_REQUEST);
+    }
+
+    /**
+     * Gets current user token.
+     *
+     * @return the current user token
+     */
+    public UserToken getCurrentUserToken() {
+        return (UserToken) RequestContextHolder.currentRequestAttributes().getAttribute(USER_TOKEN_ATTRIBUTES, RequestAttributes.SCOPE_REQUEST);
+    }
+}

+ 40 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/ErrorController.java

@@ -0,0 +1,40 @@
+package vip.xiaonuo.exam.controller;
+
+import vip.xiaonuo.exam.base.SystemCode;
+import org.springframework.boot.autoconfigure.web.ErrorProperties;
+import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
+import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+public class ErrorController extends BasicErrorController {
+
+    private static final String PATH = "/error";
+
+    public ErrorController() {
+        super(new DefaultErrorAttributes(), new ErrorProperties());
+    }
+
+    @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
+    @ResponseBody
+    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
+        Map<String, Object> error = new HashMap<>(2);
+        error.put("code", SystemCode.InnerError.getCode());
+        error.put("message", SystemCode.InnerError.getMessage());
+        return new ResponseEntity<>(error, HttpStatus.OK);
+    }
+
+
+    public String getErrorPath() {
+        return PATH;
+    }
+}

+ 56 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/DashboardController.java

@@ -0,0 +1,56 @@
+package vip.xiaonuo.exam.controller.admin;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.service.*;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.viewmodel.admin.dashboard.IndexVM;
+
+import java.util.List;
+
+@RestController("AdminDashboardController")
+@RequestMapping(value = "/api/admin/dashboard")
+public class DashboardController extends BaseApiController {
+
+    private final ExamPaperService examPaperService;
+    private final QuestionService questionService;
+    private final ExamPaperAnswerService examPaperAnswerService;
+    private final ExamPaperQuestionCustomerAnswerService examPaperQuestionCustomerAnswerService;
+    private final UserEventLogService userEventLogService;
+
+    @Autowired
+    public DashboardController(ExamPaperService examPaperService, QuestionService questionService, ExamPaperAnswerService examPaperAnswerService, ExamPaperQuestionCustomerAnswerService examPaperQuestionCustomerAnswerService, UserEventLogService userEventLogService) {
+        this.examPaperService = examPaperService;
+        this.questionService = questionService;
+        this.examPaperAnswerService = examPaperAnswerService;
+        this.examPaperQuestionCustomerAnswerService = examPaperQuestionCustomerAnswerService;
+        this.userEventLogService = userEventLogService;
+    }
+
+    @RequestMapping(value = "/index", method = RequestMethod.POST)
+    public RestResponse<IndexVM> Index() {
+        IndexVM vm = new IndexVM();
+
+        Integer examPaperCount = examPaperService.selectAllCount();
+        Integer questionCount = questionService.selectAllCount();
+        Integer doExamPaperCount = examPaperAnswerService.selectAllCount();
+        Integer doQuestionCount = examPaperQuestionCustomerAnswerService.selectAllCount();
+
+        vm.setExamPaperCount(examPaperCount);
+        vm.setQuestionCount(questionCount);
+        vm.setDoExamPaperCount(doExamPaperCount);
+        vm.setDoQuestionCount(doQuestionCount);
+
+        List<Integer> mothDayUserActionValue = userEventLogService.selectMothCount();
+        List<Integer> mothDayDoExamQuestionValue = examPaperQuestionCustomerAnswerService.selectMothCount();
+        vm.setMothDayUserActionValue(mothDayUserActionValue);
+        vm.setMothDayDoExamQuestionValue(mothDayDoExamQuestionValue);
+
+        vm.setMothDayText(DateTimeUtil.MothDay());
+        return RestResponse.ok(vm);
+    }
+}

+ 69 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/EducationController.java

@@ -0,0 +1,69 @@
+package vip.xiaonuo.exam.controller.admin;
+
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.domain.Subject;
+import vip.xiaonuo.exam.service.SubjectService;
+import vip.xiaonuo.exam.utility.PageInfoHelper;
+import vip.xiaonuo.exam.viewmodel.admin.education.SubjectEditRequestVM;
+import vip.xiaonuo.exam.viewmodel.admin.education.SubjectPageRequestVM;
+import vip.xiaonuo.exam.viewmodel.admin.education.SubjectResponseVM;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@RestController("AdminEducationController")
+@RequestMapping(value = "/api/admin/education")
+public class EducationController extends BaseApiController {
+
+    private final SubjectService subjectService;
+
+    @Autowired
+    public EducationController(SubjectService subjectService) {
+        this.subjectService = subjectService;
+    }
+
+    @RequestMapping(value = "/subject/list", method = RequestMethod.POST)
+    public RestResponse<List<Subject>> list() {
+        List<Subject> subjects = subjectService.allSubject();
+        return RestResponse.ok(subjects);
+    }
+
+    @RequestMapping(value = "/subject/page", method = RequestMethod.POST)
+    public RestResponse<PageInfo<SubjectResponseVM>> pageList(@RequestBody SubjectPageRequestVM model) {
+        PageInfo<Subject> pageInfo = subjectService.page(model);
+        PageInfo<SubjectResponseVM> page = PageInfoHelper.copyMap(pageInfo, e -> modelMapper.map(e, SubjectResponseVM.class));
+        return RestResponse.ok(page);
+    }
+
+    @RequestMapping(value = "/subject/edit", method = RequestMethod.POST)
+    public RestResponse edit(@RequestBody @Valid SubjectEditRequestVM model) {
+        Subject subject = modelMapper.map(model, Subject.class);
+        if (model.getId() == null) {
+            subject.setDeleted(false);
+            subjectService.insertByFilter(subject);
+        } else {
+            subjectService.updateByIdFilter(subject);
+        }
+        return RestResponse.ok();
+    }
+
+    @RequestMapping(value = "/subject/select/{id}", method = RequestMethod.POST)
+    public RestResponse<SubjectEditRequestVM> select(@PathVariable Integer id) {
+        Subject subject = subjectService.selectById(id);
+        SubjectEditRequestVM vm = modelMapper.map(subject, SubjectEditRequestVM.class);
+        return RestResponse.ok(vm);
+    }
+
+    @RequestMapping(value = "/subject/delete/{id}", method = RequestMethod.POST)
+    public RestResponse delete(@PathVariable Integer id) {
+        Subject subject = subjectService.selectById(id);
+        subject.setDeleted(true);
+        subjectService.updateByIdFilter(subject);
+        return RestResponse.ok();
+    }
+}

+ 57 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/ExamPaperAnswerController.java

@@ -0,0 +1,57 @@
+package vip.xiaonuo.exam.controller.admin;
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.domain.ExamPaperAnswer;
+import vip.xiaonuo.exam.domain.Subject;
+import vip.xiaonuo.exam.domain.User;
+
+import vip.xiaonuo.exam.service.ExamPaperAnswerService;
+import vip.xiaonuo.exam.service.SubjectService;
+import vip.xiaonuo.exam.service.UserService;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.utility.ExamUtil;
+import vip.xiaonuo.exam.utility.PageInfoHelper;
+import vip.xiaonuo.exam.viewmodel.student.exampaper.ExamPaperAnswerPageResponseVM;
+import vip.xiaonuo.exam.viewmodel.admin.paper.ExamPaperAnswerPageRequestVM;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@RestController("AdminExamPaperAnswerController")
+@RequestMapping(value = "/api/admin/examPaperAnswer")
+public class ExamPaperAnswerController extends BaseApiController {
+
+    private final ExamPaperAnswerService examPaperAnswerService;
+    private final SubjectService subjectService;
+    private final UserService userService;
+
+    @Autowired
+    public ExamPaperAnswerController(ExamPaperAnswerService examPaperAnswerService, SubjectService subjectService, UserService userService) {
+        this.examPaperAnswerService = examPaperAnswerService;
+        this.subjectService = subjectService;
+        this.userService = userService;
+    }
+
+
+    @RequestMapping(value = "/page", method = RequestMethod.POST)
+    public RestResponse<PageInfo<ExamPaperAnswerPageResponseVM>> pageJudgeList(@RequestBody ExamPaperAnswerPageRequestVM model) {
+        PageInfo<ExamPaperAnswer> pageInfo = examPaperAnswerService.adminPage(model);
+        PageInfo<ExamPaperAnswerPageResponseVM> page = PageInfoHelper.copyMap(pageInfo, e -> {
+            ExamPaperAnswerPageResponseVM vm = modelMapper.map(e, ExamPaperAnswerPageResponseVM.class);
+            Subject subject = subjectService.selectById(vm.getSubjectId());
+            vm.setDoTime(ExamUtil.secondToVM(e.getDoTime()));
+            vm.setSystemScore(ExamUtil.scoreToVM(e.getSystemScore()));
+            vm.setUserScore(ExamUtil.scoreToVM(e.getUserScore()));
+            vm.setPaperScore(ExamUtil.scoreToVM(e.getPaperScore()));
+            vm.setSubjectName(subject.getName());
+            vm.setCreateTime(DateTimeUtil.dateFormat(e.getCreateTime()));
+            User user = userService.selectById(e.getCreateUser());
+            vm.setUserName(user.getUserName());
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+
+
+}

+ 75 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/ExamPaperController.java

@@ -0,0 +1,75 @@
+package vip.xiaonuo.exam.controller.admin;
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.domain.ExamPaper;
+import vip.xiaonuo.exam.service.ExamPaperService;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.utility.PageInfoHelper;
+import vip.xiaonuo.exam.viewmodel.admin.exam.ExamPaperPageRequestVM;
+import vip.xiaonuo.exam.viewmodel.admin.exam.ExamPaperEditRequestVM;
+import vip.xiaonuo.exam.viewmodel.admin.exam.ExamResponseVM;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+@RestController("AdminExamPaperController")
+@RequestMapping(value = "/api/admin/exam/paper")
+public class ExamPaperController extends BaseApiController {
+
+    private final ExamPaperService examPaperService;
+
+    @Autowired
+    public ExamPaperController(ExamPaperService examPaperService) {
+        this.examPaperService = examPaperService;
+    }
+
+    @RequestMapping(value = "/page", method = RequestMethod.POST)
+    public RestResponse<PageInfo<ExamResponseVM>> pageList(@RequestBody ExamPaperPageRequestVM model) {
+        PageInfo<ExamPaper> pageInfo = examPaperService.page(model);
+        PageInfo<ExamResponseVM> page = PageInfoHelper.copyMap(pageInfo, e -> {
+            ExamResponseVM vm = modelMapper.map(e, ExamResponseVM.class);
+            vm.setCreateTime(DateTimeUtil.dateFormat(e.getCreateTime()));
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+
+
+
+    @RequestMapping(value = "/taskExamPage", method = RequestMethod.POST)
+    public RestResponse<PageInfo<ExamResponseVM>> taskExamPageList(@RequestBody ExamPaperPageRequestVM model) {
+        PageInfo<ExamPaper> pageInfo = examPaperService.taskExamPage(model);
+        PageInfo<ExamResponseVM> page = PageInfoHelper.copyMap(pageInfo, e -> {
+            ExamResponseVM vm = modelMapper.map(e, ExamResponseVM.class);
+            vm.setCreateTime(DateTimeUtil.dateFormat(e.getCreateTime()));
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+
+
+
+    @RequestMapping(value = "/edit", method = RequestMethod.POST)
+    public RestResponse<ExamPaperEditRequestVM> edit(@RequestBody @Valid ExamPaperEditRequestVM model) {
+        ExamPaper examPaper = examPaperService.savePaperFromVM(model, getCurrentUser());
+        ExamPaperEditRequestVM newVM = examPaperService.examPaperToVM(examPaper.getId());
+        return RestResponse.ok(newVM);
+    }
+
+    @RequestMapping(value = "/select/{id}", method = RequestMethod.POST)
+    public RestResponse<ExamPaperEditRequestVM> select(@PathVariable Integer id) {
+        ExamPaperEditRequestVM vm = examPaperService.examPaperToVM(id);
+        return RestResponse.ok(vm);
+    }
+
+    @RequestMapping(value = "/delete/{id}", method = RequestMethod.POST)
+    public RestResponse delete(@PathVariable Integer id) {
+        ExamPaper examPaper = examPaperService.selectById(id);
+        examPaper.setDeleted(true);
+        examPaperService.updateByIdFilter(examPaper);
+        return RestResponse.ok();
+    }
+}

+ 85 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/MessageController.java

@@ -0,0 +1,85 @@
+package vip.xiaonuo.exam.controller.admin;
+
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.domain.Message;
+import vip.xiaonuo.exam.domain.MessageUser;
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.service.MessageService;
+import vip.xiaonuo.exam.service.UserService;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.utility.PageInfoHelper;
+import vip.xiaonuo.exam.viewmodel.admin.message.MessagePageRequestVM;
+import vip.xiaonuo.exam.viewmodel.admin.message.MessageResponseVM;
+import vip.xiaonuo.exam.viewmodel.admin.message.MessageSendVM;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController("AdminMessageController")
+@RequestMapping(value = "/api/admin/message")
+public class MessageController extends BaseApiController {
+
+    private final MessageService messageService;
+    private final UserService userService;
+
+    @Autowired
+    public MessageController(MessageService messageService, UserService userService) {
+        this.messageService = messageService;
+        this.userService = userService;
+    }
+
+    @RequestMapping(value = "/page", method = RequestMethod.POST)
+    public RestResponse<PageInfo<MessageResponseVM>> pageList(@RequestBody MessagePageRequestVM model) {
+        PageInfo<Message> pageInfo = messageService.page(model);
+        List<Integer> ids = pageInfo.getList().stream().map(d -> d.getId()).collect(Collectors.toList());
+        List<MessageUser> messageUsers = ids.size() == 0 ? null : messageService.selectByMessageIds(ids);
+        PageInfo<MessageResponseVM> page = PageInfoHelper.copyMap(pageInfo, m -> {
+            MessageResponseVM vm = modelMapper.map(m, MessageResponseVM.class);
+            String receives = messageUsers.stream().filter(d -> d.getMessageId().equals(m.getId())).map(d -> d.getReceiveUserName())
+                    .collect(Collectors.joining(","));
+            vm.setReceives(receives);
+            vm.setCreateTime(DateTimeUtil.dateFormat(m.getCreateTime()));
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+
+
+    @RequestMapping(value = "/send", method = RequestMethod.POST)
+    public RestResponse send(@RequestBody @Valid MessageSendVM model) {
+        User user = getCurrentUser();
+        List<User> receiveUser = userService.selectByIds(model.getReceiveUserIds());
+        Date now = new Date();
+        Message message = new Message();
+        message.setTitle(model.getTitle());
+        message.setContent(model.getContent());
+        message.setCreateTime(now);
+        message.setReadCount(0);
+        message.setReceiveUserCount(receiveUser.size());
+        message.setSendUserId(user.getId());
+        message.setSendUserName(user.getUserName());
+        message.setSendRealName(user.getRealName());
+        List<MessageUser> messageUsers = receiveUser.stream().map(d -> {
+            MessageUser messageUser = new MessageUser();
+            messageUser.setCreateTime(now);
+            messageUser.setReaded(false);
+            messageUser.setReceiveRealName(d.getRealName());
+            messageUser.setReceiveUserId(d.getId());
+            messageUser.setReceiveUserName(d.getUserName());
+            return messageUser;
+        }).collect(Collectors.toList());
+        messageService.sendMessage(message, messageUsers);
+        return RestResponse.ok();
+    }
+
+}

+ 104 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/QuestionController.java

@@ -0,0 +1,104 @@
+package vip.xiaonuo.exam.controller.admin;
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.base.SystemCode;
+import vip.xiaonuo.exam.domain.Question;
+import vip.xiaonuo.exam.domain.TextContent;
+import vip.xiaonuo.exam.domain.enums.QuestionTypeEnum;
+import vip.xiaonuo.exam.domain.question.QuestionObject;
+import vip.xiaonuo.exam.service.QuestionService;
+import vip.xiaonuo.exam.service.TextContentService;
+
+import vip.xiaonuo.exam.utility.*;
+import vip.xiaonuo.exam.viewmodel.admin.question.QuestionEditRequestVM;
+import vip.xiaonuo.exam.viewmodel.admin.question.QuestionPageRequestVM;
+import vip.xiaonuo.exam.viewmodel.admin.question.QuestionResponseVM;
+import com.github.pagehelper.PageInfo;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+@RestController("AdminQuestionController")
+@RequestMapping(value = "/api/admin/question")
+public class QuestionController extends BaseApiController {
+
+    private final QuestionService questionService;
+    private final TextContentService textContentService;
+
+    @Autowired
+    public QuestionController(QuestionService questionService, TextContentService textContentService) {
+        this.questionService = questionService;
+        this.textContentService = textContentService;
+    }
+
+    @RequestMapping(value = "/page", method = RequestMethod.POST)
+    public RestResponse<PageInfo<QuestionResponseVM>> pageList(@RequestBody QuestionPageRequestVM model) {
+        PageInfo<Question> pageInfo = questionService.page(model);
+        PageInfo<QuestionResponseVM> page = PageInfoHelper.copyMap(pageInfo, q -> {
+            QuestionResponseVM vm = modelMapper.map(q, QuestionResponseVM.class);
+            vm.setCreateTime(DateTimeUtil.dateFormat(q.getCreateTime()));
+            vm.setScore(ExamUtil.scoreToVM(q.getScore()));
+            TextContent textContent = textContentService.selectById(q.getInfoTextContentId());
+            QuestionObject questionObject = JsonUtil.toJsonObject(textContent.getContent(), QuestionObject.class);
+            String clearHtml = HtmlUtil.clear(questionObject.getTitleContent());
+            vm.setShortTitle(clearHtml);
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+
+    @RequestMapping(value = "/edit", method = RequestMethod.POST)
+    public RestResponse edit(@RequestBody @Valid QuestionEditRequestVM model) {
+        RestResponse validQuestionEditRequestResult = validQuestionEditRequestVM(model);
+        if (validQuestionEditRequestResult.getCode() != SystemCode.OK.getCode()) {
+            return validQuestionEditRequestResult;
+        }
+
+        if (null == model.getId()) {
+            questionService.insertFullQuestion(model, getCurrentUser().getId());
+        } else {
+            questionService.updateFullQuestion(model);
+        }
+
+        return RestResponse.ok();
+    }
+
+    @RequestMapping(value = "/select/{id}", method = RequestMethod.POST)
+    public RestResponse<QuestionEditRequestVM> select(@PathVariable Integer id) {
+        QuestionEditRequestVM newVM = questionService.getQuestionEditRequestVM(id);
+        return RestResponse.ok(newVM);
+    }
+
+
+    @RequestMapping(value = "/delete/{id}", method = RequestMethod.POST)
+    public RestResponse delete(@PathVariable Integer id) {
+        Question question = questionService.selectById(id);
+        question.setDeleted(true);
+        questionService.updateByIdFilter(question);
+        return RestResponse.ok();
+    }
+
+    private RestResponse validQuestionEditRequestVM(QuestionEditRequestVM model) {
+        int qType = model.getQuestionType().intValue();
+        boolean requireCorrect = qType == QuestionTypeEnum.SingleChoice.getCode() || qType == QuestionTypeEnum.TrueFalse.getCode();
+        if (requireCorrect) {
+            if (StringUtils.isBlank(model.getCorrect())) {
+                String errorMsg = ErrorUtil.parameterErrorFormat("correct", "不能为空");
+                return new RestResponse<>(SystemCode.ParameterValidError.getCode(), errorMsg);
+            }
+        }
+
+        if (qType == QuestionTypeEnum.GapFilling.getCode()) {
+            Integer fillSumScore = model.getItems().stream().mapToInt(d -> ExamUtil.scoreFromVM(d.getScore())).sum();
+            Integer questionScore = ExamUtil.scoreFromVM(model.getScore());
+            if (!fillSumScore.equals(questionScore)) {
+                String errorMsg = ErrorUtil.parameterErrorFormat("score", "空分数和与题目总分不相等");
+                return new RestResponse<>(SystemCode.ParameterValidError.getCode(), errorMsg);
+            }
+        }
+        return RestResponse.ok();
+    }
+}

+ 63 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/TaskController.java

@@ -0,0 +1,63 @@
+package vip.xiaonuo.exam.controller.admin;
+
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.domain.TaskExam;
+import vip.xiaonuo.exam.service.TaskExamService;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.utility.PageInfoHelper;
+import vip.xiaonuo.exam.viewmodel.admin.task.TaskPageRequestVM;
+import vip.xiaonuo.exam.viewmodel.admin.task.TaskPageResponseVM;
+import vip.xiaonuo.exam.viewmodel.admin.task.TaskRequestVM;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+@RestController("AdminTaskController")
+@RequestMapping(value = "/api/admin/task")
+public class TaskController extends BaseApiController {
+
+    private final TaskExamService taskExamService;
+
+    @Autowired
+    public TaskController(TaskExamService taskExamService) {
+        this.taskExamService = taskExamService;
+    }
+
+    @RequestMapping(value = "/page", method = RequestMethod.POST)
+    public RestResponse<PageInfo<TaskPageResponseVM>> pageList(@RequestBody TaskPageRequestVM model) {
+        PageInfo<TaskExam> pageInfo = taskExamService.page(model);
+        PageInfo<TaskPageResponseVM> page = PageInfoHelper.copyMap(pageInfo, m -> {
+            TaskPageResponseVM vm = modelMapper.map(m, TaskPageResponseVM.class);
+            vm.setCreateTime(DateTimeUtil.dateFormat(m.getCreateTime()));
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+
+
+    @RequestMapping(value = "/edit", method = RequestMethod.POST)
+    public RestResponse edit(@RequestBody @Valid TaskRequestVM model) {
+        taskExamService.edit(model, getCurrentUser());
+        TaskRequestVM vm = taskExamService.taskExamToVM(model.getId());
+        return RestResponse.ok(vm);
+    }
+
+
+    @RequestMapping(value = "/select/{id}", method = RequestMethod.POST)
+    public RestResponse<TaskRequestVM> select(@PathVariable Integer id) {
+        TaskRequestVM vm = taskExamService.taskExamToVM(id);
+        return RestResponse.ok(vm);
+    }
+
+    @RequestMapping(value = "/delete/{id}", method = RequestMethod.POST)
+    public RestResponse delete(@PathVariable Integer id) {
+        TaskExam taskExam = taskExamService.selectById(id);
+        taskExam.setDeleted(true);
+        taskExamService.updateByIdFilter(taskExam);
+        return RestResponse.ok();
+    }
+}

+ 103 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/UploadController.java

@@ -0,0 +1,103 @@
+package vip.xiaonuo.exam.controller.admin;
+
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.configuration.property.SystemConfig;
+import vip.xiaonuo.exam.service.FileUpload;
+import vip.xiaonuo.exam.service.UserService;
+import vip.xiaonuo.exam.viewmodel.admin.file.UeditorConfigVM;
+import vip.xiaonuo.exam.viewmodel.admin.file.UploadResultVM;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+
+@RequestMapping("/api/admin/upload")
+@RestController("AdminUploadController")
+public class UploadController extends BaseApiController {
+
+    private final FileUpload fileUpload;
+    private final SystemConfig systemConfig;
+    private static final Logger logger = LoggerFactory.getLogger(UploadController.class);
+    private static final String IMAGE_UPLOAD = "imgUpload";
+    private static final String IMAGE_UPLOAD_FILE = "upFile";
+    private final UserService userService;
+
+    @Autowired
+    public UploadController(FileUpload fileUpload, SystemConfig systemConfig, UserService userService) {
+        this.fileUpload = fileUpload;
+        this.systemConfig = systemConfig;
+        this.userService = userService;
+    }
+
+    @ResponseBody
+    @RequestMapping("/configAndUpload")
+    public Object upload(HttpServletRequest request, HttpServletResponse response) {
+        String action = request.getParameter("action");
+        if (action.equals(IMAGE_UPLOAD)) {
+            try {
+                MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
+                MultipartFile multipartFile = multipartHttpServletRequest.getFile(IMAGE_UPLOAD_FILE);
+                long attachSize = multipartFile.getSize();
+                String imgName = multipartFile.getOriginalFilename();
+                String filePath;
+                try (InputStream inputStream = multipartFile.getInputStream()) {
+                    filePath = fileUpload.uploadFile(inputStream, attachSize, imgName);
+                }
+                String imageType = imgName.substring(imgName.lastIndexOf("."));
+                UploadResultVM uploadResultVM = new UploadResultVM();
+                uploadResultVM.setOriginal(imgName);
+                uploadResultVM.setName(imgName);
+                uploadResultVM.setUrl(filePath);
+                uploadResultVM.setSize(multipartFile.getSize());
+                uploadResultVM.setType(imageType);
+                uploadResultVM.setState("SUCCESS");
+                return uploadResultVM;
+            } catch (IOException e) {
+                logger.error(e.getMessage(), e);
+            }
+        } else {
+            UeditorConfigVM ueditorConfigVM = new UeditorConfigVM();
+            ueditorConfigVM.setImageActionName(IMAGE_UPLOAD);
+            ueditorConfigVM.setImageFieldName(IMAGE_UPLOAD_FILE);
+            ueditorConfigVM.setImageMaxSize(2048000L);
+            ueditorConfigVM.setImageAllowFiles(Arrays.asList(".png", ".jpg", ".jpeg", ".gif", ".bmp"));
+            ueditorConfigVM.setImageCompressEnable(true);
+            ueditorConfigVM.setImageCompressBorder(1600);
+            ueditorConfigVM.setImageInsertAlign("none");
+            ueditorConfigVM.setImageUrlPrefix("");
+            ueditorConfigVM.setImagePathFormat("");
+            return ueditorConfigVM;
+        }
+        return null;
+    }
+
+
+    @RequestMapping("/image")
+    @ResponseBody
+    public RestResponse questionUploadAndReadExcel(HttpServletRequest request) {
+        MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
+        MultipartFile multipartFile = multipartHttpServletRequest.getFile("file");
+        long attachSize = multipartFile.getSize();
+        String imgName = multipartFile.getOriginalFilename();
+        try (InputStream inputStream = multipartFile.getInputStream()) {
+            String filePath = fileUpload.uploadFile(inputStream, attachSize, imgName);
+            userService.changePicture(getCurrentUser(), filePath);
+            return RestResponse.ok(filePath);
+        } catch (IOException e) {
+            return RestResponse.fail(2, e.getMessage());
+        }
+    }
+
+
+}

+ 152 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/admin/UserController.java

@@ -0,0 +1,152 @@
+package vip.xiaonuo.exam.controller.admin;
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.domain.other.KeyValue;
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.domain.UserEventLog;
+import vip.xiaonuo.exam.domain.enums.UserStatusEnum;
+import vip.xiaonuo.exam.service.AuthenticationService;
+import vip.xiaonuo.exam.service.UserEventLogService;
+import vip.xiaonuo.exam.service.UserService;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+
+import vip.xiaonuo.exam.utility.PageInfoHelper;
+import com.github.pagehelper.PageInfo;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import vip.xiaonuo.exam.viewmodel.admin.user.*;
+
+import javax.validation.Valid;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+
+@RestController("AdminUserController")
+@RequestMapping(value = "/api/admin/user")
+public class UserController extends BaseApiController {
+
+    private final UserService userService;
+    private final UserEventLogService userEventLogService;
+    private final AuthenticationService authenticationService;
+
+    @Autowired
+    public UserController(UserService userService, UserEventLogService userEventLogService, AuthenticationService authenticationService) {
+        this.userService = userService;
+        this.userEventLogService = userEventLogService;
+        this.authenticationService = authenticationService;
+    }
+
+
+    @RequestMapping(value = "/page/list", method = RequestMethod.POST)
+    public RestResponse<PageInfo<UserResponseVM>> pageList(@RequestBody UserPageRequestVM model) {
+        PageInfo<User> pageInfo = userService.userPage(model);
+        PageInfo<UserResponseVM> page = PageInfoHelper.copyMap(pageInfo, d -> UserResponseVM.from(d));
+        return RestResponse.ok(page);
+    }
+
+
+    @RequestMapping(value = "/event/page/list", method = RequestMethod.POST)
+    public RestResponse<PageInfo<UserEventLogVM>> eventPageList(@RequestBody UserEventPageRequestVM model) {
+        PageInfo<UserEventLog> pageInfo = userEventLogService.page(model);
+        PageInfo<UserEventLogVM> page = PageInfoHelper.copyMap(pageInfo, d -> {
+            UserEventLogVM vm = modelMapper.map(d, UserEventLogVM.class);
+            vm.setCreateTime(DateTimeUtil.dateFormat(d.getCreateTime()));
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+
+    @RequestMapping(value = "/select/{id}", method = RequestMethod.POST)
+    public RestResponse<UserResponseVM> select(@PathVariable Integer id) {
+        User user = userService.getUserById(id);
+        UserResponseVM userVm = UserResponseVM.from(user);
+        return RestResponse.ok(userVm);
+    }
+
+    @RequestMapping(value = "/current", method = RequestMethod.POST)
+    public RestResponse<UserResponseVM> current() {
+        User user = getCurrentUser();
+        UserResponseVM userVm = UserResponseVM.from(user);
+        return RestResponse.ok(userVm);
+    }
+
+
+    @RequestMapping(value = "/edit", method = RequestMethod.POST)
+    public RestResponse<User> edit(@RequestBody @Valid UserCreateVM model) {
+        if (model.getId() == null) {  //create
+            User existUser = userService.getUserByUserName(model.getUserName());
+            if (null != existUser) {
+                return new RestResponse<>(2, "用户已存在");
+            }
+
+            if (StringUtils.isBlank(model.getPassword())) {
+                return new RestResponse<>(3, "密码不能为空");
+            }
+        }
+        if (StringUtils.isBlank(model.getBirthDay())) {
+            model.setBirthDay(null);
+        }
+        User user = modelMapper.map(model, User.class);
+
+        if (model.getId() == null) {
+            String encodePwd = authenticationService.pwdEncode(model.getPassword());
+            user.setPassword(encodePwd);
+            user.setUserUuid(UUID.randomUUID().toString());
+            user.setCreateTime(new Date());
+            user.setLastActiveTime(new Date());
+            user.setDeleted(false);
+            userService.insertByFilter(user);
+        } else {
+            if (!StringUtils.isBlank(model.getPassword())) {
+                String encodePwd = authenticationService.pwdEncode(model.getPassword());
+                user.setPassword(encodePwd);
+            }
+            user.setModifyTime(new Date());
+            userService.updateByIdFilter(user);
+        }
+        return RestResponse.ok(user);
+    }
+
+
+    @RequestMapping(value = "/update", method = RequestMethod.POST)
+    public RestResponse update(@RequestBody @Valid UserUpdateVM model) {
+        User user = userService.selectById(getCurrentUser().getId());
+        modelMapper.map(model, user);
+        user.setModifyTime(new Date());
+        userService.updateByIdFilter(user);
+        return RestResponse.ok();
+    }
+
+
+    @RequestMapping(value = "/changeStatus/{id}", method = RequestMethod.POST)
+    public RestResponse<Integer> changeStatus(@PathVariable Integer id) {
+        User user = userService.getUserById(id);
+        UserStatusEnum userStatusEnum = UserStatusEnum.fromCode(user.getStatus());
+        Integer newStatus = userStatusEnum == UserStatusEnum.Enable ? UserStatusEnum.Disable.getCode() : UserStatusEnum.Enable.getCode();
+        user.setStatus(newStatus);
+        user.setModifyTime(new Date());
+        userService.updateByIdFilter(user);
+        return RestResponse.ok(newStatus);
+    }
+
+
+    @RequestMapping(value = "/delete/{id}", method = RequestMethod.POST)
+    public RestResponse delete(@PathVariable Integer id) {
+        User user = userService.getUserById(id);
+        user.setDeleted(true);
+        userService.updateByIdFilter(user);
+        return RestResponse.ok();
+    }
+
+
+    @RequestMapping(value = "/selectByUserName", method = RequestMethod.POST)
+    public RestResponse<List<KeyValue>> selectByUserName(@RequestBody String userName) {
+        List<KeyValue> keyValues = userService.selectByUserName(userName);
+        return RestResponse.ok(keyValues);
+    }
+
+}

+ 128 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/DashboardController.java

@@ -0,0 +1,128 @@
+package vip.xiaonuo.exam.controller.student;
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.domain.TaskExam;
+import vip.xiaonuo.exam.domain.TaskExamCustomerAnswer;
+import vip.xiaonuo.exam.domain.TextContent;
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.domain.enums.ExamPaperTypeEnum;
+import vip.xiaonuo.exam.domain.task.TaskItemAnswerObject;
+import vip.xiaonuo.exam.domain.task.TaskItemObject;
+
+import vip.xiaonuo.exam.service.*;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.utility.JsonUtil;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+import vip.xiaonuo.exam.viewmodel.student.dashboard.*;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController("StudentDashboardController")
+@RequestMapping(value = "/api/student/dashboard")
+public class DashboardController extends BaseApiController {
+
+    private final UserService userService;
+    private final ExamPaperService examPaperService;
+    private final QuestionService questionService;
+    private final TaskExamService taskExamService;
+    private final TaskExamCustomerAnswerService taskExamCustomerAnswerService;
+    private final TextContentService textContentService;
+
+    @Autowired
+    public DashboardController(UserService userService, ExamPaperService examPaperService, QuestionService questionService, TaskExamService taskExamService, TaskExamCustomerAnswerService taskExamCustomerAnswerService, TextContentService textContentService) {
+        this.userService = userService;
+        this.examPaperService = examPaperService;
+        this.questionService = questionService;
+        this.taskExamService = taskExamService;
+        this.taskExamCustomerAnswerService = taskExamCustomerAnswerService;
+        this.textContentService = textContentService;
+    }
+
+    @RequestMapping(value = "/index", method = RequestMethod.POST)
+    public RestResponse<IndexVM> index() {
+        IndexVM indexVM = new IndexVM();
+        User user = getCurrentUser();
+
+        PaperFilter fixedPaperFilter = new PaperFilter();
+        fixedPaperFilter.setGradeLevel(user.getUserLevel());
+        fixedPaperFilter.setExamPaperType(ExamPaperTypeEnum.Fixed.getCode());
+        indexVM.setFixedPaper(examPaperService.indexPaper(fixedPaperFilter));
+
+        PaperFilter timeLimitPaperFilter = new PaperFilter();
+        timeLimitPaperFilter.setDateTime(new Date());
+        timeLimitPaperFilter.setGradeLevel(user.getUserLevel());
+        timeLimitPaperFilter.setExamPaperType(ExamPaperTypeEnum.TimeLimit.getCode());
+
+        List<PaperInfo> limitPaper = examPaperService.indexPaper(timeLimitPaperFilter);
+        List<PaperInfoVM> paperInfoVMS = limitPaper.stream().map(d -> {
+            PaperInfoVM vm = modelMapper.map(d, PaperInfoVM.class);
+            vm.setStartTime(DateTimeUtil.dateFormat(d.getLimitStartTime()));
+            vm.setEndTime(DateTimeUtil.dateFormat(d.getLimitEndTime()));
+            return vm;
+        }).collect(Collectors.toList());
+        indexVM.setTimeLimitPaper(paperInfoVMS);
+        return RestResponse.ok(indexVM);
+    }
+
+
+    @RequestMapping(value = "/task", method = RequestMethod.POST)
+    public RestResponse<List<TaskItemVm>> task() {
+        User user = getCurrentUser();
+        List<TaskExam> taskExams = taskExamService.getByGradeLevel(user.getUserLevel());
+        if (taskExams.size() == 0) {
+            return RestResponse.ok(new ArrayList<>());
+        }
+        List<Integer> tIds = taskExams.stream().map(taskExam -> taskExam.getId()).collect(Collectors.toList());
+        List<TaskExamCustomerAnswer> taskExamCustomerAnswers = taskExamCustomerAnswerService.selectByTUid(tIds, user.getId());
+        List<TaskItemVm> vm = taskExams.stream().map(t -> {
+            TaskItemVm itemVm = new TaskItemVm();
+            itemVm.setId(t.getId());
+            itemVm.setTitle(t.getTitle());
+            TaskExamCustomerAnswer taskExamCustomerAnswer = taskExamCustomerAnswers.stream()
+                    .filter(tc -> tc.getTaskExamId().equals(t.getId())).findFirst().orElse(null);
+            List<TaskItemPaperVm> paperItemVMS = getTaskItemPaperVm(t.getFrameTextContentId(), taskExamCustomerAnswer);
+            itemVm.setPaperItems(paperItemVMS);
+            return itemVm;
+        }).collect(Collectors.toList());
+        return RestResponse.ok(vm);
+    }
+
+
+    private List<TaskItemPaperVm> getTaskItemPaperVm(Integer tFrameId, TaskExamCustomerAnswer taskExamCustomerAnswers) {
+        TextContent textContent = textContentService.selectById(tFrameId);
+        List<TaskItemObject> paperItems = JsonUtil.toJsonListObject(textContent.getContent(), TaskItemObject.class);
+
+        List<TaskItemAnswerObject> answerPaperItems = null;
+        if (null != taskExamCustomerAnswers) {
+            TextContent answerTextContent = textContentService.selectById(taskExamCustomerAnswers.getTextContentId());
+            answerPaperItems = JsonUtil.toJsonListObject(answerTextContent.getContent(), TaskItemAnswerObject.class);
+        }
+
+
+        List<TaskItemAnswerObject> finalAnswerPaperItems = answerPaperItems;
+        return paperItems.stream().map(p -> {
+                    TaskItemPaperVm ivm = new TaskItemPaperVm();
+                    ivm.setExamPaperId(p.getExamPaperId());
+                    ivm.setExamPaperName(p.getExamPaperName());
+                    if (null != finalAnswerPaperItems) {
+                        finalAnswerPaperItems.stream()
+                                .filter(a -> a.getExamPaperId().equals(p.getExamPaperId()))
+                                .findFirst()
+                                .ifPresent(a -> {
+                                    ivm.setExamPaperAnswerId(a.getExamPaperAnswerId());
+                                    ivm.setStatus(a.getStatus());
+                                });
+                    }
+                    return ivm;
+                }
+        ).collect(Collectors.toList());
+    }
+}

+ 47 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/EducationController.java

@@ -0,0 +1,47 @@
+package vip.xiaonuo.exam.controller.student;
+
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.domain.Subject;
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.service.SubjectService;
+import vip.xiaonuo.exam.viewmodel.student.education.SubjectEditRequestVM;
+import vip.xiaonuo.exam.viewmodel.student.education.SubjectVM;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController("StudentEducationController")
+@RequestMapping(value = "/api/student/education")
+public class EducationController extends BaseApiController {
+
+    private final SubjectService subjectService;
+
+    @Autowired
+    public EducationController(SubjectService subjectService) {
+        this.subjectService = subjectService;
+    }
+
+    @RequestMapping(value = "/subject/list", method = RequestMethod.POST)
+    public RestResponse<List<SubjectVM>> list() {
+        User user = getCurrentUser();
+        List<Subject> subjects = subjectService.getSubjectByLevel(user.getUserLevel());
+        List<SubjectVM> subjectVMS = subjects.stream().map(d -> {
+            SubjectVM subjectVM = modelMapper.map(d, SubjectVM.class);
+            subjectVM.setId(String.valueOf(d.getId()));
+            return subjectVM;
+        }).collect(Collectors.toList());
+        return RestResponse.ok(subjectVMS);
+    }
+
+    @RequestMapping(value = "/subject/select/{id}", method = RequestMethod.POST)
+    public RestResponse<SubjectEditRequestVM> select(@PathVariable Integer id) {
+        Subject subject = subjectService.selectById(id);
+        SubjectEditRequestVM vm = modelMapper.map(subject, SubjectEditRequestVM.class);
+        return RestResponse.ok(vm);
+    }
+
+}

+ 120 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/ExamPaperAnswerController.java

@@ -0,0 +1,120 @@
+package vip.xiaonuo.exam.controller.student;
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+
+import vip.xiaonuo.exam.domain.*;
+import vip.xiaonuo.exam.domain.enums.ExamPaperAnswerStatusEnum;
+import vip.xiaonuo.exam.event.CalculateExamPaperAnswerCompleteEvent;
+import vip.xiaonuo.exam.event.UserEvent;
+import vip.xiaonuo.exam.service.ExamPaperAnswerService;
+import vip.xiaonuo.exam.service.ExamPaperService;
+import vip.xiaonuo.exam.service.SubjectService;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.utility.ExamUtil;
+import vip.xiaonuo.exam.utility.PageInfoHelper;
+import vip.xiaonuo.exam.viewmodel.admin.exam.ExamPaperEditRequestVM;
+import vip.xiaonuo.exam.viewmodel.student.exam.ExamPaperReadVM;
+import vip.xiaonuo.exam.viewmodel.student.exam.ExamPaperSubmitVM;
+import vip.xiaonuo.exam.viewmodel.student.exampaper.ExamPaperAnswerPageResponseVM;
+import vip.xiaonuo.exam.viewmodel.student.exampaper.ExamPaperAnswerPageVM;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.Date;
+
+@RestController("StudentExamPaperAnswerController")
+@RequestMapping(value = "/api/student/exampaper/answer")
+public class ExamPaperAnswerController extends BaseApiController {
+
+    private final ExamPaperAnswerService examPaperAnswerService;
+    private final ExamPaperService examPaperService;
+    private final SubjectService subjectService;
+    private final ApplicationEventPublisher eventPublisher;
+
+    @Autowired
+    public ExamPaperAnswerController(ExamPaperAnswerService examPaperAnswerService, ExamPaperService examPaperService, SubjectService subjectService, ApplicationEventPublisher eventPublisher) {
+        this.examPaperAnswerService = examPaperAnswerService;
+        this.examPaperService = examPaperService;
+        this.subjectService = subjectService;
+        this.eventPublisher = eventPublisher;
+    }
+
+
+    @RequestMapping(value = "/pageList", method = RequestMethod.POST)
+    public RestResponse<PageInfo<ExamPaperAnswerPageResponseVM>> pageList(@RequestBody @Valid ExamPaperAnswerPageVM model) {
+        model.setCreateUser(getCurrentUser().getId());
+        PageInfo<ExamPaperAnswer> pageInfo = examPaperAnswerService.studentPage(model);
+        PageInfo<ExamPaperAnswerPageResponseVM> page = PageInfoHelper.copyMap(pageInfo, e -> {
+            ExamPaperAnswerPageResponseVM vm = modelMapper.map(e, ExamPaperAnswerPageResponseVM.class);
+            Subject subject = subjectService.selectById(vm.getSubjectId());
+            vm.setDoTime(ExamUtil.secondToVM(e.getDoTime()));
+            vm.setSystemScore(ExamUtil.scoreToVM(e.getSystemScore()));
+            vm.setUserScore(ExamUtil.scoreToVM(e.getUserScore()));
+            vm.setPaperScore(ExamUtil.scoreToVM(e.getPaperScore()));
+            vm.setSubjectName(subject.getName());
+            vm.setCreateTime(DateTimeUtil.dateFormat(e.getCreateTime()));
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+
+
+    @RequestMapping(value = "/answerSubmit", method = RequestMethod.POST)
+    public RestResponse answerSubmit(@RequestBody @Valid ExamPaperSubmitVM examPaperSubmitVM) {
+        User user = getCurrentUser();
+        ExamPaperAnswerInfo examPaperAnswerInfo = examPaperAnswerService.calculateExamPaperAnswer(examPaperSubmitVM, user);
+        if (null == examPaperAnswerInfo) {
+            return RestResponse.fail(2, "试卷不能重复做");
+        }
+        ExamPaperAnswer examPaperAnswer = examPaperAnswerInfo.getExamPaperAnswer();
+        Integer userScore = examPaperAnswer.getUserScore();
+        String scoreVm = ExamUtil.scoreToVM(userScore);
+        UserEventLog userEventLog = new UserEventLog(user.getId(), user.getUserName(), user.getRealName(), new Date());
+        String content = user.getUserName() + " 提交试卷:" + examPaperAnswerInfo.getExamPaper().getName()
+                + " 得分:" + scoreVm
+                + " 耗时:" + ExamUtil.secondToVM(examPaperAnswer.getDoTime());
+        userEventLog.setContent(content);
+        eventPublisher.publishEvent(new CalculateExamPaperAnswerCompleteEvent(examPaperAnswerInfo));
+        eventPublisher.publishEvent(new UserEvent(userEventLog));
+        return RestResponse.ok(scoreVm);
+    }
+
+
+    @RequestMapping(value = "/edit", method = RequestMethod.POST)
+    public RestResponse edit(@RequestBody @Valid ExamPaperSubmitVM examPaperSubmitVM) {
+        boolean notJudge = examPaperSubmitVM.getAnswerItems().stream().anyMatch(i -> i.getDoRight() == null && i.getScore() == null);
+        if (notJudge) {
+            return RestResponse.fail(2, "有未批改题目");
+        }
+
+        ExamPaperAnswer examPaperAnswer = examPaperAnswerService.selectById(examPaperSubmitVM.getId());
+        ExamPaperAnswerStatusEnum examPaperAnswerStatusEnum = ExamPaperAnswerStatusEnum.fromCode(examPaperAnswer.getStatus());
+        if (examPaperAnswerStatusEnum == ExamPaperAnswerStatusEnum.Complete) {
+            return RestResponse.fail(3, "试卷已完成");
+        }
+        String score = examPaperAnswerService.judge(examPaperSubmitVM);
+        User user = getCurrentUser();
+        UserEventLog userEventLog = new UserEventLog(user.getId(), user.getUserName(), user.getRealName(), new Date());
+        String content = user.getUserName() + " 批改试卷:" + examPaperAnswer.getPaperName() + " 得分:" + score;
+        userEventLog.setContent(content);
+        eventPublisher.publishEvent(new UserEvent(userEventLog));
+        return RestResponse.ok(score);
+    }
+
+    @RequestMapping(value = "/read/{id}", method = RequestMethod.POST)
+    public RestResponse<ExamPaperReadVM> read(@PathVariable Integer id) {
+        ExamPaperAnswer examPaperAnswer = examPaperAnswerService.selectById(id);
+        ExamPaperReadVM vm = new ExamPaperReadVM();
+        ExamPaperEditRequestVM paper = examPaperService.examPaperToVM(examPaperAnswer.getExamPaperId());
+        ExamPaperSubmitVM answer = examPaperAnswerService.examPaperAnswerToVM(examPaperAnswer.getId());
+        vm.setPaper(paper);
+        vm.setAnswer(answer);
+        return RestResponse.ok(vm);
+    }
+
+
+}

+ 53 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/ExamPaperController.java

@@ -0,0 +1,53 @@
+package vip.xiaonuo.exam.controller.student;
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.domain.ExamPaper;
+import vip.xiaonuo.exam.service.ExamPaperAnswerService;
+import vip.xiaonuo.exam.service.ExamPaperService;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.utility.PageInfoHelper;
+import vip.xiaonuo.exam.viewmodel.admin.exam.ExamPaperEditRequestVM;
+import vip.xiaonuo.exam.viewmodel.student.exam.ExamPaperPageResponseVM;
+import vip.xiaonuo.exam.viewmodel.student.exam.ExamPaperPageVM;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+@RestController("StudentExamPaperController")
+@RequestMapping(value = "/api/student/exam/paper")
+public class ExamPaperController extends BaseApiController {
+
+    private final ExamPaperService examPaperService;
+    private final ExamPaperAnswerService examPaperAnswerService;
+    private final ApplicationEventPublisher eventPublisher;
+
+    @Autowired
+    public ExamPaperController(ExamPaperService examPaperService, ExamPaperAnswerService examPaperAnswerService, ApplicationEventPublisher eventPublisher) {
+        this.examPaperService = examPaperService;
+        this.examPaperAnswerService = examPaperAnswerService;
+        this.eventPublisher = eventPublisher;
+    }
+
+
+    @RequestMapping(value = "/select/{id}", method = RequestMethod.POST)
+    public RestResponse<ExamPaperEditRequestVM> select(@PathVariable Integer id) {
+        ExamPaperEditRequestVM vm = examPaperService.examPaperToVM(id);
+        return RestResponse.ok(vm);
+    }
+
+
+    @RequestMapping(value = "/pageList", method = RequestMethod.POST)
+    public RestResponse<PageInfo<ExamPaperPageResponseVM>> pageList(@RequestBody @Valid ExamPaperPageVM model) {
+        PageInfo<ExamPaper> pageInfo = examPaperService.studentPage(model);
+        PageInfo<ExamPaperPageResponseVM> page = PageInfoHelper.copyMap(pageInfo, e -> {
+            ExamPaperPageResponseVM vm = modelMapper.map(e, ExamPaperPageResponseVM.class);
+            vm.setCreateTime(DateTimeUtil.dateFormat(e.getCreateTime()));
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+}

+ 73 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/QuestionAnswerController.java

@@ -0,0 +1,73 @@
+package vip.xiaonuo.exam.controller.student;
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.domain.ExamPaperQuestionCustomerAnswer;
+import vip.xiaonuo.exam.domain.Subject;
+import vip.xiaonuo.exam.domain.TextContent;
+import vip.xiaonuo.exam.domain.question.QuestionObject;
+import vip.xiaonuo.exam.service.ExamPaperQuestionCustomerAnswerService;
+import vip.xiaonuo.exam.service.QuestionService;
+import vip.xiaonuo.exam.service.SubjectService;
+import vip.xiaonuo.exam.service.TextContentService;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.utility.HtmlUtil;
+import vip.xiaonuo.exam.utility.JsonUtil;
+import vip.xiaonuo.exam.utility.PageInfoHelper;
+import vip.xiaonuo.exam.viewmodel.admin.question.QuestionEditRequestVM;
+import vip.xiaonuo.exam.viewmodel.student.exam.ExamPaperSubmitItemVM;
+import vip.xiaonuo.exam.viewmodel.student.question.answer.QuestionAnswerVM;
+import vip.xiaonuo.exam.viewmodel.student.question.answer.QuestionPageStudentRequestVM;
+import vip.xiaonuo.exam.viewmodel.student.question.answer.QuestionPageStudentResponseVM;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@RestController("StudentQuestionAnswerController")
+@RequestMapping(value = "/api/student/question/answer")
+public class QuestionAnswerController extends BaseApiController {
+
+    private final ExamPaperQuestionCustomerAnswerService examPaperQuestionCustomerAnswerService;
+    private final QuestionService questionService;
+    private final TextContentService textContentService;
+    private final SubjectService subjectService;
+
+    @Autowired
+    public QuestionAnswerController(ExamPaperQuestionCustomerAnswerService examPaperQuestionCustomerAnswerService, QuestionService questionService, TextContentService textContentService, SubjectService subjectService) {
+        this.examPaperQuestionCustomerAnswerService = examPaperQuestionCustomerAnswerService;
+        this.questionService = questionService;
+        this.textContentService = textContentService;
+        this.subjectService = subjectService;
+    }
+
+    @RequestMapping(value = "/page", method = RequestMethod.POST)
+    public RestResponse<PageInfo<QuestionPageStudentResponseVM>> pageList(@RequestBody QuestionPageStudentRequestVM model) {
+        model.setCreateUser(getCurrentUser().getId());
+        PageInfo<ExamPaperQuestionCustomerAnswer> pageInfo = examPaperQuestionCustomerAnswerService.studentPage(model);
+        PageInfo<QuestionPageStudentResponseVM> page = PageInfoHelper.copyMap(pageInfo, q -> {
+            Subject subject = subjectService.selectById(q.getSubjectId());
+            QuestionPageStudentResponseVM vm = modelMapper.map(q, QuestionPageStudentResponseVM.class);
+            vm.setCreateTime(DateTimeUtil.dateFormat(q.getCreateTime()));
+            TextContent textContent = textContentService.selectById(q.getQuestionTextContentId());
+            QuestionObject questionObject = JsonUtil.toJsonObject(textContent.getContent(), QuestionObject.class);
+            String clearHtml = HtmlUtil.clear(questionObject.getTitleContent());
+            vm.setShortTitle(clearHtml);
+            vm.setSubjectName(subject.getName());
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+
+
+    @RequestMapping(value = "/select/{id}", method = RequestMethod.POST)
+    public RestResponse<QuestionAnswerVM> select(@PathVariable Integer id) {
+        QuestionAnswerVM vm = new QuestionAnswerVM();
+        ExamPaperQuestionCustomerAnswer examPaperQuestionCustomerAnswer = examPaperQuestionCustomerAnswerService.selectById(id);
+        ExamPaperSubmitItemVM questionAnswerVM = examPaperQuestionCustomerAnswerService.examPaperQuestionCustomerAnswerToVM(examPaperQuestionCustomerAnswer);
+        QuestionEditRequestVM questionVM = questionService.getQuestionEditRequestVM(examPaperQuestionCustomerAnswer.getQuestionId());
+        vm.setQuestionVM(questionVM);
+        vm.setQuestionAnswerVM(questionAnswerVM);
+        return RestResponse.ok(vm);
+    }
+
+}

+ 18 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/QuestionController.java

@@ -0,0 +1,18 @@
+package vip.xiaonuo.exam.controller.student;
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.service.QuestionService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@RestController("StudentQuestionController")
+@RequestMapping(value = "/api/student/question")
+public class QuestionController extends BaseApiController {
+
+    private final QuestionService questionService;
+
+    @Autowired
+    public QuestionController(QuestionService questionService) {
+        this.questionService = questionService;
+    }
+}

+ 51 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/UploadController.java

@@ -0,0 +1,51 @@
+package vip.xiaonuo.exam.controller.student;
+
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.service.FileUpload;
+import vip.xiaonuo.exam.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+@RequestMapping("/api/student/upload")
+@RestController("StudentUploadController")
+public class UploadController extends BaseApiController {
+
+    private final FileUpload fileUpload;
+    private final UserService userService;
+
+    @Autowired
+    public UploadController(FileUpload fileUpload, UserService userService) {
+        this.fileUpload = fileUpload;
+        this.userService = userService;
+    }
+
+
+    @RequestMapping("/image")
+    @ResponseBody
+    public RestResponse questionUploadAndReadExcel(HttpServletRequest request) {
+        MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
+        MultipartFile multipartFile = multipartHttpServletRequest.getFile("file");
+        long attachSize = multipartFile.getSize();
+        String imgName = multipartFile.getOriginalFilename();
+        try (InputStream inputStream = multipartFile.getInputStream()) {
+            String filePath = fileUpload.uploadFile(inputStream, attachSize, imgName);
+            userService.changePicture(getCurrentUser(), filePath);
+            return RestResponse.ok(filePath);
+        } catch (IOException e) {
+            return RestResponse.fail(2, e.getMessage());
+        }
+    }
+
+
+}

+ 140 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/student/UserController.java

@@ -0,0 +1,140 @@
+package vip.xiaonuo.exam.controller.student;
+
+import vip.xiaonuo.exam.base.BaseApiController;
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.domain.Message;
+import vip.xiaonuo.exam.domain.MessageUser;
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.domain.UserEventLog;
+import vip.xiaonuo.exam.domain.enums.RoleEnum;
+import vip.xiaonuo.exam.domain.enums.UserStatusEnum;
+import vip.xiaonuo.exam.event.UserEvent;
+import vip.xiaonuo.exam.service.AuthenticationService;
+import vip.xiaonuo.exam.service.MessageService;
+import vip.xiaonuo.exam.service.UserEventLogService;
+import vip.xiaonuo.exam.service.UserService;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.utility.PageInfoHelper;
+
+import com.github.pagehelper.PageInfo;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.web.bind.annotation.*;
+import vip.xiaonuo.exam.viewmodel.student.user.*;
+
+import javax.validation.Valid;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@RestController("StudentUserController")
+@RequestMapping(value = "/api/student/user")
+public class UserController extends BaseApiController {
+
+    private final UserService userService;
+    private final UserEventLogService userEventLogService;
+    private final MessageService messageService;
+    private final AuthenticationService authenticationService;
+    private final ApplicationEventPublisher eventPublisher;
+
+    @Autowired
+    public UserController(UserService userService, UserEventLogService userEventLogService, MessageService messageService, AuthenticationService authenticationService, ApplicationEventPublisher eventPublisher) {
+        this.userService = userService;
+        this.userEventLogService = userEventLogService;
+        this.messageService = messageService;
+        this.authenticationService = authenticationService;
+        this.eventPublisher = eventPublisher;
+    }
+
+    @RequestMapping(value = "/current", method = RequestMethod.POST)
+    public RestResponse<UserResponseVM> current() {
+        User user = getCurrentUser();
+        UserResponseVM userVm = UserResponseVM.from(user);
+        return RestResponse.ok(userVm);
+    }
+
+
+    @RequestMapping(value = "/register", method = RequestMethod.POST)
+    public RestResponse register(@RequestBody @Valid UserRegisterVM model) {
+        User existUser = userService.getUserByUserName(model.getUserName());
+        if (null != existUser) {
+            return new RestResponse<>(2, "用户已存在");
+        }
+        User user = modelMapper.map(model, User.class);
+        String encodePwd = authenticationService.pwdEncode(model.getPassword());
+        user.setUserUuid(UUID.randomUUID().toString());
+        user.setPassword(encodePwd);
+        user.setRole(RoleEnum.STUDENT.getCode());
+        user.setStatus(UserStatusEnum.Enable.getCode());
+        user.setLastActiveTime(new Date());
+        user.setCreateTime(new Date());
+        user.setDeleted(false);
+        userService.insertByFilter(user);
+        UserEventLog userEventLog = new UserEventLog(user.getId(), user.getUserName(), user.getRealName(), new Date());
+        userEventLog.setContent("欢迎 " + user.getUserName() + " 注册来到学之思开源考试系统");
+        eventPublisher.publishEvent(new UserEvent(userEventLog));
+        return RestResponse.ok();
+    }
+
+
+    @RequestMapping(value = "/update", method = RequestMethod.POST)
+    public RestResponse update(@RequestBody @Valid UserUpdateVM model) {
+        if (StringUtils.isBlank(model.getBirthDay())) {
+            model.setBirthDay(null);
+        }
+        User user = userService.selectById(getCurrentUser().getId());
+        modelMapper.map(model, user);
+        user.setModifyTime(new Date());
+        userService.updateByIdFilter(user);
+        UserEventLog userEventLog = new UserEventLog(user.getId(), user.getUserName(), user.getRealName(), new Date());
+        userEventLog.setContent(user.getUserName() + " 更新了个人资料");
+        eventPublisher.publishEvent(new UserEvent(userEventLog));
+        return RestResponse.ok();
+    }
+
+    @RequestMapping(value = "/log", method = RequestMethod.POST)
+    public RestResponse<List<UserEventLogVM>> log() {
+        User user = getCurrentUser();
+        List<UserEventLog> userEventLogs = userEventLogService.getUserEventLogByUserId(user.getId());
+        List<UserEventLogVM> userEventLogVMS = userEventLogs.stream().map(d -> {
+            UserEventLogVM vm = modelMapper.map(d, UserEventLogVM.class);
+            vm.setCreateTime(DateTimeUtil.dateFormat(d.getCreateTime()));
+            return vm;
+        }).collect(Collectors.toList());
+        return RestResponse.ok(userEventLogVMS);
+    }
+
+    @RequestMapping(value = "/message/page", method = RequestMethod.POST)
+    public RestResponse<PageInfo<MessageResponseVM>> messagePageList(@RequestBody MessageRequestVM messageRequestVM) {
+        messageRequestVM.setReceiveUserId(getCurrentUser().getId());
+        PageInfo<MessageUser> messageUserPageInfo = messageService.studentPage(messageRequestVM);
+        List<Integer> ids = messageUserPageInfo.getList().stream().map(d -> d.getMessageId()).collect(Collectors.toList());
+        List<Message> messages = ids.size() != 0 ? messageService.selectMessageByIds(ids) : null;
+        PageInfo<MessageResponseVM> page = PageInfoHelper.copyMap(messageUserPageInfo, e -> {
+            MessageResponseVM vm = modelMapper.map(e, MessageResponseVM.class);
+            messages.stream().filter(d -> e.getMessageId().equals(d.getId())).findFirst().ifPresent(message -> {
+                vm.setTitle(message.getTitle());
+                vm.setContent(message.getContent());
+                vm.setSendUserName(message.getSendUserName());
+            });
+            vm.setCreateTime(DateTimeUtil.dateFormat(e.getCreateTime()));
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+
+    @RequestMapping(value = "/message/unreadCount", method = RequestMethod.POST)
+    public RestResponse unReadCount() {
+        Integer count = messageService.unReadCount(getCurrentUser().getId());
+        return RestResponse.ok(count);
+    }
+
+    @RequestMapping(value = "/message/read/{id}", method = RequestMethod.POST)
+    public RestResponse read(@PathVariable Integer id) {
+        messageService.read(id);
+        return RestResponse.ok();
+    }
+
+}

+ 22 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/wx/BaseWXApiController.java

@@ -0,0 +1,22 @@
+package vip.xiaonuo.exam.controller.wx;
+
+import vip.xiaonuo.exam.context.WxContext;
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.domain.UserToken;
+import vip.xiaonuo.exam.utility.ModelMapperSingle;
+import org.modelmapper.ModelMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class BaseWXApiController {
+    protected final static ModelMapper modelMapper = ModelMapperSingle.Instance();
+    @Autowired
+    private WxContext wxContext;
+
+    protected User getCurrentUser() {
+        return wxContext.getCurrentUser();
+    }
+
+    protected UserToken getUserToken() {
+        return wxContext.getCurrentUserToken();
+    }
+}

+ 85 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/wx/student/AuthController.java

@@ -0,0 +1,85 @@
+package vip.xiaonuo.exam.controller.wx.student;
+
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.configuration.property.SystemConfig;
+import vip.xiaonuo.exam.controller.wx.BaseWXApiController;
+import vip.xiaonuo.exam.domain.UserToken;
+import vip.xiaonuo.exam.domain.enums.UserStatusEnum;
+import vip.xiaonuo.exam.service.AuthenticationService;
+import vip.xiaonuo.exam.service.UserService;
+import vip.xiaonuo.exam.service.UserTokenService;
+import vip.xiaonuo.exam.utility.WxUtil;
+import vip.xiaonuo.exam.viewmodel.wx.student.user.BindInfo;
+import vip.xiaonuo.exam.domain.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+
+
+@Controller("WXStudentAuthController")
+@RequestMapping(value = "/api/wx/student/auth")
+@ResponseBody
+public class AuthController extends BaseWXApiController {
+
+    private final SystemConfig systemConfig;
+    private final AuthenticationService authenticationService;
+    private final UserService userService;
+    private final UserTokenService userTokenService;
+
+    @Autowired
+    public AuthController(SystemConfig systemConfig, AuthenticationService authenticationService, UserService userService, UserTokenService userTokenService) {
+        this.systemConfig = systemConfig;
+        this.authenticationService = authenticationService;
+        this.userService = userService;
+        this.userTokenService = userTokenService;
+    }
+
+    @RequestMapping(value = "/bind", method = RequestMethod.POST)
+    public RestResponse bind(@Valid BindInfo model) {
+        User user = userService.getUserByUserName(model.getUserName());
+        if (user == null) {
+            return RestResponse.fail(2, "用户名或密码错误");
+        }
+        boolean result = authenticationService.authUser(user, model.getUserName(), model.getPassword());
+        if (!result) {
+            return RestResponse.fail(2, "用户名或密码错误");
+        }
+        UserStatusEnum userStatusEnum = UserStatusEnum.fromCode(user.getStatus());
+        if (UserStatusEnum.Disable == userStatusEnum) {
+            return RestResponse.fail(3, "用户被禁用");
+        }
+        String code = model.getCode();
+        String openid = WxUtil.getOpenId(systemConfig.getWx().getAppid(), systemConfig.getWx().getSecret(), code);
+        if (null == openid) {
+            return RestResponse.fail(4, "获取微信OpenId失败");
+        }
+        user.setWxOpenId(openid);
+        UserToken userToken = userTokenService.bind(user);
+        return RestResponse.ok(userToken.getToken());
+    }
+
+
+    @RequestMapping(value = "/checkBind", method = RequestMethod.POST)
+    public RestResponse checkBind(@Valid @NotBlank String code) {
+        String openid = WxUtil.getOpenId(systemConfig.getWx().getAppid(), systemConfig.getWx().getSecret(), code);
+        if (null == openid) {
+            return RestResponse.fail(3, "获取微信OpenId失败");
+        }
+        UserToken userToken = userTokenService.checkBind(openid);
+        if (null != userToken) {
+            return RestResponse.ok(userToken.getToken());
+        }
+        return RestResponse.fail(2, "用户未绑定");
+    }
+
+
+    @RequestMapping(value = "/unBind", method = RequestMethod.POST)
+    public RestResponse unBind() {
+        UserToken userToken = getUserToken();
+        userTokenService.unBind(userToken);
+        return RestResponse.ok();
+    }
+}

+ 129 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/wx/student/DashboardController.java

@@ -0,0 +1,129 @@
+package vip.xiaonuo.exam.controller.wx.student;
+
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.controller.wx.BaseWXApiController;
+import vip.xiaonuo.exam.domain.TaskExam;
+import vip.xiaonuo.exam.domain.TaskExamCustomerAnswer;
+import vip.xiaonuo.exam.domain.TextContent;
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.domain.enums.ExamPaperTypeEnum;
+import vip.xiaonuo.exam.domain.task.TaskItemAnswerObject;
+import vip.xiaonuo.exam.domain.task.TaskItemObject;
+import vip.xiaonuo.exam.service.ExamPaperService;
+import vip.xiaonuo.exam.service.TaskExamCustomerAnswerService;
+import vip.xiaonuo.exam.service.TaskExamService;
+import vip.xiaonuo.exam.service.TextContentService;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.utility.JsonUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import vip.xiaonuo.exam.viewmodel.student.dashboard.*;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+
+@Controller("WXStudentDashboardController")
+@RequestMapping(value = "/api/wx/student/dashboard")
+@ResponseBody
+public class DashboardController extends BaseWXApiController {
+
+    private final ExamPaperService examPaperService;
+    private final TextContentService textContentService;
+    private final TaskExamService taskExamService;
+    private final TaskExamCustomerAnswerService taskExamCustomerAnswerService;
+
+    @Autowired
+    public DashboardController(ExamPaperService examPaperService, TextContentService textContentService, TaskExamService taskExamService, TaskExamCustomerAnswerService taskExamCustomerAnswerService) {
+        this.examPaperService = examPaperService;
+        this.textContentService = textContentService;
+        this.taskExamService = taskExamService;
+        this.taskExamCustomerAnswerService = taskExamCustomerAnswerService;
+    }
+
+    @RequestMapping(value = "/index", method = RequestMethod.POST)
+    public RestResponse<IndexVM> index() {
+        IndexVM indexVM = new IndexVM();
+        User user = getCurrentUser();
+
+        PaperFilter fixedPaperFilter = new PaperFilter();
+        fixedPaperFilter.setGradeLevel(user.getUserLevel());
+        fixedPaperFilter.setExamPaperType(ExamPaperTypeEnum.Fixed.getCode());
+        indexVM.setFixedPaper(examPaperService.indexPaper(fixedPaperFilter));
+
+        PaperFilter timeLimitPaperFilter = new PaperFilter();
+        timeLimitPaperFilter.setDateTime(new Date());
+        timeLimitPaperFilter.setGradeLevel(user.getUserLevel());
+        timeLimitPaperFilter.setExamPaperType(ExamPaperTypeEnum.TimeLimit.getCode());
+
+        List<PaperInfo> limitPaper = examPaperService.indexPaper(timeLimitPaperFilter);
+        List<PaperInfoVM> paperInfoVMS = limitPaper.stream().map(d -> {
+            PaperInfoVM vm = modelMapper.map(d, PaperInfoVM.class);
+            vm.setStartTime(DateTimeUtil.dateFormat(d.getLimitStartTime()));
+            vm.setEndTime(DateTimeUtil.dateFormat(d.getLimitEndTime()));
+            return vm;
+        }).collect(Collectors.toList());
+        indexVM.setTimeLimitPaper(paperInfoVMS);
+        return RestResponse.ok(indexVM);
+    }
+
+    @RequestMapping(value = "/task", method = RequestMethod.POST)
+    public RestResponse<List<TaskItemVm>> task() {
+        User user = getCurrentUser();
+        List<TaskExam> taskExams = taskExamService.getByGradeLevel(user.getUserLevel());
+        if (taskExams.size() == 0) {
+            return RestResponse.ok(new ArrayList<>());
+        }
+        List<Integer> tIds = taskExams.stream().map(taskExam -> taskExam.getId()).collect(Collectors.toList());
+        List<TaskExamCustomerAnswer> taskExamCustomerAnswers = taskExamCustomerAnswerService.selectByTUid(tIds, user.getId());
+        List<TaskItemVm> vm = taskExams.stream().map(t -> {
+            TaskItemVm itemVm = new TaskItemVm();
+            itemVm.setId(t.getId());
+            itemVm.setTitle(t.getTitle());
+            TaskExamCustomerAnswer taskExamCustomerAnswer = taskExamCustomerAnswers.stream()
+                    .filter(tc -> tc.getTaskExamId().equals(t.getId())).findFirst().orElse(null);
+            List<TaskItemPaperVm> paperItemVMS = getTaskItemPaperVm(t.getFrameTextContentId(), taskExamCustomerAnswer);
+            itemVm.setPaperItems(paperItemVMS);
+            return itemVm;
+        }).collect(Collectors.toList());
+        return RestResponse.ok(vm);
+    }
+
+
+    private List<TaskItemPaperVm> getTaskItemPaperVm(Integer tFrameId, TaskExamCustomerAnswer taskExamCustomerAnswers) {
+        TextContent textContent = textContentService.selectById(tFrameId);
+        List<TaskItemObject> paperItems = JsonUtil.toJsonListObject(textContent.getContent(), TaskItemObject.class);
+
+        List<TaskItemAnswerObject> answerPaperItems = null;
+        if (null != taskExamCustomerAnswers) {
+            TextContent answerTextContent = textContentService.selectById(taskExamCustomerAnswers.getTextContentId());
+            answerPaperItems = JsonUtil.toJsonListObject(answerTextContent.getContent(), TaskItemAnswerObject.class);
+        }
+
+
+        List<TaskItemAnswerObject> finalAnswerPaperItems = answerPaperItems;
+        return paperItems.stream().map(p -> {
+                    TaskItemPaperVm ivm = new TaskItemPaperVm();
+                    ivm.setExamPaperId(p.getExamPaperId());
+                    ivm.setExamPaperName(p.getExamPaperName());
+                    if (null != finalAnswerPaperItems) {
+                        finalAnswerPaperItems.stream()
+                                .filter(a -> a.getExamPaperId().equals(p.getExamPaperId()))
+                                .findFirst()
+                                .ifPresent(a -> {
+                                    ivm.setExamPaperAnswerId(a.getExamPaperAnswerId());
+                                    ivm.setStatus(a.getStatus());
+                                });
+                    }
+                    return ivm;
+                }
+        ).collect(Collectors.toList());
+    }
+
+
+}

+ 135 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/wx/student/ExamPaperAnswerController.java

@@ -0,0 +1,135 @@
+package vip.xiaonuo.exam.controller.wx.student;
+
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.controller.wx.BaseWXApiController;
+import vip.xiaonuo.exam.domain.*;
+import vip.xiaonuo.exam.domain.enums.QuestionTypeEnum;
+import vip.xiaonuo.exam.event.CalculateExamPaperAnswerCompleteEvent;
+import vip.xiaonuo.exam.event.UserEvent;
+import vip.xiaonuo.exam.service.ExamPaperAnswerService;
+import vip.xiaonuo.exam.service.ExamPaperService;
+import vip.xiaonuo.exam.service.SubjectService;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.utility.ExamUtil;
+import vip.xiaonuo.exam.utility.PageInfoHelper;
+import vip.xiaonuo.exam.viewmodel.admin.exam.ExamPaperEditRequestVM;
+import vip.xiaonuo.exam.viewmodel.student.exampaper.ExamPaperAnswerPageResponseVM;
+import vip.xiaonuo.exam.viewmodel.student.exampaper.ExamPaperAnswerPageVM;
+import com.github.pagehelper.PageInfo;
+import vip.xiaonuo.exam.viewmodel.student.exam.ExamPaperReadVM;
+import vip.xiaonuo.exam.viewmodel.student.exam.ExamPaperSubmitItemVM;
+import vip.xiaonuo.exam.viewmodel.student.exam.ExamPaperSubmitVM;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+@Controller("WXStudentExamPaperAnswerController")
+@RequestMapping(value = "/api/wx/student/exampaper/answer")
+@ResponseBody
+public class ExamPaperAnswerController extends BaseWXApiController {
+
+    private final ExamPaperAnswerService examPaperAnswerService;
+    private final SubjectService subjectService;
+    private final ApplicationEventPublisher eventPublisher;
+    private final ExamPaperService examPaperService;
+
+    @Autowired
+    public ExamPaperAnswerController(ExamPaperAnswerService examPaperAnswerService, SubjectService subjectService, ApplicationEventPublisher eventPublisher, ExamPaperService examPaperService) {
+        this.examPaperAnswerService = examPaperAnswerService;
+        this.subjectService = subjectService;
+        this.eventPublisher = eventPublisher;
+        this.examPaperService = examPaperService;
+    }
+
+    @RequestMapping(value = "/pageList", method = RequestMethod.POST)
+    public RestResponse<PageInfo<ExamPaperAnswerPageResponseVM>> pageList(@Valid ExamPaperAnswerPageVM model) {
+        model.setCreateUser(getCurrentUser().getId());
+        PageInfo<ExamPaperAnswer> pageInfo = examPaperAnswerService.studentPage(model);
+        PageInfo<ExamPaperAnswerPageResponseVM> page = PageInfoHelper.copyMap(pageInfo, e -> {
+            ExamPaperAnswerPageResponseVM vm = modelMapper.map(e, ExamPaperAnswerPageResponseVM.class);
+            Subject subject = subjectService.selectById(vm.getSubjectId());
+            vm.setDoTime(ExamUtil.secondToVM(e.getDoTime()));
+            vm.setSystemScore(ExamUtil.scoreToVM(e.getSystemScore()));
+            vm.setUserScore(ExamUtil.scoreToVM(e.getUserScore()));
+            vm.setPaperScore(ExamUtil.scoreToVM(e.getPaperScore()));
+            vm.setSubjectName(subject.getName());
+            vm.setCreateTime(DateTimeUtil.dateFormat(e.getCreateTime()));
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+
+
+    @RequestMapping(value = "/answerSubmit", method = RequestMethod.POST)
+    public RestResponse answerSubmit(HttpServletRequest request) {
+        ExamPaperSubmitVM examPaperSubmitVM = requestToExamPaperSubmitVM(request);
+        User user = getCurrentUser();
+        ExamPaperAnswerInfo examPaperAnswerInfo = examPaperAnswerService.calculateExamPaperAnswer(examPaperSubmitVM, user);
+        if (null == examPaperAnswerInfo) {
+            return RestResponse.fail(2, "试卷不能重复做");
+        }
+        ExamPaperAnswer examPaperAnswer = examPaperAnswerInfo.getExamPaperAnswer();
+        Integer userScore = examPaperAnswer.getUserScore();
+        String scoreVm = ExamUtil.scoreToVM(userScore);
+        UserEventLog userEventLog = new UserEventLog(user.getId(), user.getUserName(), user.getRealName(), new Date());
+        String content = user.getUserName() + " 提交试卷:" + examPaperAnswerInfo.getExamPaper().getName()
+                + " 得分:" + scoreVm
+                + " 耗时:" + ExamUtil.secondToVM(examPaperAnswer.getDoTime());
+        userEventLog.setContent(content);
+        eventPublisher.publishEvent(new CalculateExamPaperAnswerCompleteEvent(examPaperAnswerInfo));
+        eventPublisher.publishEvent(new UserEvent(userEventLog));
+        return RestResponse.ok(scoreVm);
+    }
+
+    private ExamPaperSubmitVM requestToExamPaperSubmitVM(HttpServletRequest request) {
+        ExamPaperSubmitVM examPaperSubmitVM = new ExamPaperSubmitVM();
+        examPaperSubmitVM.setId(Integer.parseInt(request.getParameter("id")));
+        examPaperSubmitVM.setDoTime(Integer.parseInt(request.getParameter("doTime")));
+        List<String> parameterNames = Collections.list(request.getParameterNames()).stream()
+                .filter(n -> n.contains("_"))
+                .collect(Collectors.toList());
+        //题目答案按序号分组
+        Map<String, List<String>> questionGroup = parameterNames.stream().collect(Collectors.groupingBy(p -> p.substring(0, p.indexOf("_"))));
+        List<ExamPaperSubmitItemVM> answerItems = new ArrayList<>();
+        questionGroup.forEach((k, v) -> {
+            ExamPaperSubmitItemVM examPaperSubmitItemVM = new ExamPaperSubmitItemVM();
+            String p = v.get(0);
+            String[] keys = p.split("_");
+            examPaperSubmitItemVM.setQuestionId(Integer.parseInt(keys[1]));
+            examPaperSubmitItemVM.setItemOrder(Integer.parseInt(keys[0]));
+            QuestionTypeEnum typeEnum = QuestionTypeEnum.fromCode(Integer.parseInt(keys[2]));
+            if (v.size() == 1) {
+                String content = request.getParameter(p);
+                examPaperSubmitItemVM.setContent(content);
+                if (typeEnum == QuestionTypeEnum.MultipleChoice) {
+                    examPaperSubmitItemVM.setContentArray(Arrays.asList(content.split(",")));
+                }
+            } else {  //多个空 填空题
+                List<String> answers = v.stream().sorted(Comparator.comparingInt(ExamUtil::lastNum)).map(inputKey -> request.getParameter(inputKey)).collect(Collectors.toList());
+                examPaperSubmitItemVM.setContentArray(answers);
+            }
+            answerItems.add(examPaperSubmitItemVM);
+        });
+        examPaperSubmitVM.setAnswerItems(answerItems);
+        return examPaperSubmitVM;
+    }
+
+
+    @PostMapping(value = "/read/{id}")
+    public RestResponse<ExamPaperReadVM> read(@PathVariable Integer id) {
+        ExamPaperReadVM vm = new ExamPaperReadVM();
+        ExamPaperAnswer examPaperAnswer = examPaperAnswerService.selectById(id);
+        ExamPaperEditRequestVM paper = examPaperService.examPaperToVM(examPaperAnswer.getExamPaperId());
+        ExamPaperSubmitVM answer = examPaperAnswerService.examPaperAnswerToVM(examPaperAnswer.getId());
+        vm.setPaper(paper);
+        vm.setAnswer(answer);
+        return RestResponse.ok(vm);
+    }
+}

+ 57 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/wx/student/ExamPaperController.java

@@ -0,0 +1,57 @@
+package vip.xiaonuo.exam.controller.wx.student;
+
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.controller.wx.BaseWXApiController;
+import vip.xiaonuo.exam.domain.ExamPaper;
+import vip.xiaonuo.exam.domain.Subject;
+import vip.xiaonuo.exam.service.ExamPaperService;
+import vip.xiaonuo.exam.service.SubjectService;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.utility.PageInfoHelper;
+import vip.xiaonuo.exam.viewmodel.admin.exam.ExamPaperEditRequestVM;
+import vip.xiaonuo.exam.viewmodel.student.exam.ExamPaperPageResponseVM;
+import vip.xiaonuo.exam.viewmodel.student.exam.ExamPaperPageVM;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+
+@Controller("WXStudentExamController")
+@RequestMapping(value = "/api/wx/student/exampaper")
+@ResponseBody
+public class ExamPaperController extends BaseWXApiController {
+
+    private final ExamPaperService examPaperService;
+    private final SubjectService subjectService;
+
+    @Autowired
+    public ExamPaperController(ExamPaperService examPaperService, SubjectService subjectService) {
+        this.examPaperService = examPaperService;
+        this.subjectService = subjectService;
+    }
+
+
+    @RequestMapping(value = "/select/{id}", method = RequestMethod.POST)
+    public RestResponse<ExamPaperEditRequestVM> select(@PathVariable Integer id) {
+        ExamPaperEditRequestVM vm = examPaperService.examPaperToVM(id);
+        return RestResponse.ok(vm);
+    }
+
+
+    @RequestMapping(value = "/pageList", method = RequestMethod.POST)
+    public RestResponse<PageInfo<ExamPaperPageResponseVM>> pageList(@Valid ExamPaperPageVM model) {
+        model.setLevelId(getCurrentUser().getUserLevel());
+        PageInfo<ExamPaper> pageInfo = examPaperService.studentPage(model);
+        PageInfo<ExamPaperPageResponseVM> page = PageInfoHelper.copyMap(pageInfo, e -> {
+            ExamPaperPageResponseVM vm = modelMapper.map(e, ExamPaperPageResponseVM.class);
+            Subject subject = subjectService.selectById(vm.getSubjectId());
+            vm.setSubjectName(subject.getName());
+            vm.setCreateTime(DateTimeUtil.dateFormat(e.getCreateTime()));
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+}

+ 150 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/controller/wx/student/UserController.java

@@ -0,0 +1,150 @@
+package vip.xiaonuo.exam.controller.wx.student;
+
+import vip.xiaonuo.exam.base.RestResponse;
+import vip.xiaonuo.exam.controller.wx.BaseWXApiController;
+import vip.xiaonuo.exam.domain.Message;
+import vip.xiaonuo.exam.domain.MessageUser;
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.domain.UserEventLog;
+import vip.xiaonuo.exam.domain.enums.RoleEnum;
+import vip.xiaonuo.exam.domain.enums.UserStatusEnum;
+import vip.xiaonuo.exam.event.UserEvent;
+import vip.xiaonuo.exam.service.AuthenticationService;
+import vip.xiaonuo.exam.service.MessageService;
+import vip.xiaonuo.exam.service.UserEventLogService;
+import vip.xiaonuo.exam.service.UserService;
+import vip.xiaonuo.exam.utility.DateTimeUtil;
+import vip.xiaonuo.exam.utility.PageInfoHelper;
+
+import com.github.pagehelper.PageInfo;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import vip.xiaonuo.exam.viewmodel.student.user.*;
+
+import javax.validation.Valid;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+
+@Controller("WXStudentUserController")
+@RequestMapping(value = "/api/wx/student/user")
+@ResponseBody
+public class UserController extends BaseWXApiController {
+
+    private final UserService userService;
+    private final UserEventLogService userEventLogService;
+    private final MessageService messageService;
+    private final AuthenticationService authenticationService;
+    private final ApplicationEventPublisher eventPublisher;
+
+    @Autowired
+    public UserController(UserService userService, UserEventLogService userEventLogService, MessageService messageService, AuthenticationService authenticationService, ApplicationEventPublisher eventPublisher) {
+        this.userService = userService;
+        this.userEventLogService = userEventLogService;
+        this.messageService = messageService;
+        this.authenticationService = authenticationService;
+        this.eventPublisher = eventPublisher;
+    }
+
+    @RequestMapping(value = "/current", method = RequestMethod.POST)
+    public RestResponse<UserResponseVM> current() {
+        User user = getCurrentUser();
+        UserResponseVM userVm = UserResponseVM.from(user);
+        userVm.setBirthDay(DateTimeUtil.dateShortFormat(user.getBirthDay()));
+        return RestResponse.ok(userVm);
+    }
+
+    @RequestMapping(value = "/register", method = RequestMethod.POST)
+    public RestResponse register(@Valid UserRegisterVM model) {
+        User existUser = userService.getUserByUserName(model.getUserName());
+        if (null != existUser) {
+            return new RestResponse<>(2, "用户已存在");
+        }
+        User user = modelMapper.map(model, User.class);
+        String encodePwd = authenticationService.pwdEncode(model.getPassword());
+        user.setUserUuid(UUID.randomUUID().toString());
+        user.setPassword(encodePwd);
+        user.setRole(RoleEnum.STUDENT.getCode());
+        user.setStatus(UserStatusEnum.Enable.getCode());
+        user.setLastActiveTime(new Date());
+        user.setCreateTime(new Date());
+        user.setDeleted(false);
+        userService.insertByFilter(user);
+        UserEventLog userEventLog = new UserEventLog(user.getId(), user.getUserName(), user.getRealName(), new Date());
+        userEventLog.setContent("欢迎 " + user.getUserName() + " 注册来到学之思开源考试系统");
+        eventPublisher.publishEvent(new UserEvent(userEventLog));
+        return RestResponse.ok();
+    }
+
+    @RequestMapping(value = "/update", method = RequestMethod.POST)
+    public RestResponse<UserResponseVM> update(@Valid UserUpdateVM model) {
+        if (StringUtils.isBlank(model.getBirthDay())) {
+            model.setBirthDay(null);
+        }
+        User user = userService.selectById(getCurrentUser().getId());
+        modelMapper.map(model, user);
+        user.setModifyTime(new Date());
+        userService.updateByIdFilter(user);
+        UserEventLog userEventLog = new UserEventLog(user.getId(), user.getUserName(), user.getRealName(), new Date());
+        userEventLog.setContent(user.getUserName() + " 更新了个人资料");
+        eventPublisher.publishEvent(new UserEvent(userEventLog));
+        UserResponseVM userVm = UserResponseVM.from(user);
+        return RestResponse.ok(userVm);
+    }
+
+    @RequestMapping(value = "/log", method = RequestMethod.POST)
+    public RestResponse<List<UserEventLogVM>> log() {
+        User user = getCurrentUser();
+        List<UserEventLog> userEventLogs = userEventLogService.getUserEventLogByUserId(user.getId());
+        List<UserEventLogVM> userEventLogVMS = userEventLogs.stream().map(d -> {
+            UserEventLogVM vm = modelMapper.map(d, UserEventLogVM.class);
+            vm.setCreateTime(DateTimeUtil.dateFormat(d.getCreateTime()));
+            return vm;
+        }).collect(Collectors.toList());
+        return RestResponse.ok(userEventLogVMS);
+    }
+
+    @RequestMapping(value = "/message/page", method = RequestMethod.POST)
+    public RestResponse<PageInfo<MessageResponseVM>> messagePageList(MessageRequestVM messageRequestVM) {
+        messageRequestVM.setReceiveUserId(getCurrentUser().getId());
+        PageInfo<MessageUser> messageUserPageInfo = messageService.studentPage(messageRequestVM);
+        List<Integer> ids = messageUserPageInfo.getList().stream().map(d -> d.getMessageId()).collect(Collectors.toList());
+        List<Message> messages = ids.size() != 0 ? messageService.selectMessageByIds(ids) : null;
+        PageInfo<MessageResponseVM> page = PageInfoHelper.copyMap(messageUserPageInfo, e -> {
+            MessageResponseVM vm = modelMapper.map(e, MessageResponseVM.class);
+            messages.stream().filter(d -> e.getMessageId().equals(d.getId())).findFirst().ifPresent(message -> {
+                vm.setTitle(message.getTitle());
+                vm.setContent(message.getContent());
+                vm.setSendUserName(message.getSendUserName());
+            });
+            vm.setCreateTime(DateTimeUtil.dateFormat(e.getCreateTime()));
+            return vm;
+        });
+        return RestResponse.ok(page);
+    }
+
+    @RequestMapping(value = "/message/detail/{id}", method = RequestMethod.POST)
+    public RestResponse messageDetail(@PathVariable Integer id) {
+        Message message = messageService.messageDetail(id);
+        return RestResponse.ok(message);
+    }
+
+
+    @RequestMapping(value = "/message/unreadCount", method = RequestMethod.POST)
+    public RestResponse unReadCount() {
+        Integer count = messageService.unReadCount(getCurrentUser().getId());
+        return RestResponse.ok(count);
+    }
+
+    @RequestMapping(value = "/message/read/{id}", method = RequestMethod.POST)
+    public RestResponse read(@PathVariable Integer id) {
+        messageService.read(id);
+        return RestResponse.ok();
+    }
+
+}

+ 189 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/ExamPaper.java

@@ -0,0 +1,189 @@
+package vip.xiaonuo.exam.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class ExamPaper implements Serializable {
+
+    private static final long serialVersionUID = 8509645224550501395L;
+
+    private Integer id;
+
+    /**
+     * 试卷名称
+     */
+    private String name;
+
+    /**
+     * 学科
+     */
+    private Integer subjectId;
+
+    /**
+     * 试卷类型( 1固定试卷 4.时段试卷 6.任务试卷)
+     */
+    private Integer paperType;
+
+    /**
+     * 年级
+     */
+    private Integer gradeLevel;
+
+    /**
+     * 试卷总分(千分制)
+     */
+    private Integer score;
+
+    /**
+     * 题目数量
+     */
+    private Integer questionCount;
+
+    /**
+     * 建议时长(分钟)
+     */
+    private Integer suggestTime;
+
+    /**
+     * 时段试卷 开始时间
+     */
+    private Date limitStartTime;
+
+    /**
+     * 时段试卷 结束时间
+     */
+    private Date limitEndTime;
+
+    /**
+     * 试卷框架 内容为JSON
+     */
+    private Integer frameTextContentId;
+
+    private Integer createUser;
+
+    private Date createTime;
+
+    private Boolean deleted;
+
+    private Integer taskExamId;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name == null ? null : name.trim();
+    }
+
+    public Integer getSubjectId() {
+        return subjectId;
+    }
+
+    public void setSubjectId(Integer subjectId) {
+        this.subjectId = subjectId;
+    }
+
+    public Integer getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(Integer paperType) {
+        this.paperType = paperType;
+    }
+
+    public Integer getGradeLevel() {
+        return gradeLevel;
+    }
+
+    public void setGradeLevel(Integer gradeLevel) {
+        this.gradeLevel = gradeLevel;
+    }
+
+    public Integer getScore() {
+        return score;
+    }
+
+    public void setScore(Integer score) {
+        this.score = score;
+    }
+
+    public Integer getQuestionCount() {
+        return questionCount;
+    }
+
+    public void setQuestionCount(Integer questionCount) {
+        this.questionCount = questionCount;
+    }
+
+    public Integer getSuggestTime() {
+        return suggestTime;
+    }
+
+    public void setSuggestTime(Integer suggestTime) {
+        this.suggestTime = suggestTime;
+    }
+
+    public Date getLimitStartTime() {
+        return limitStartTime;
+    }
+
+    public void setLimitStartTime(Date limitStartTime) {
+        this.limitStartTime = limitStartTime;
+    }
+
+    public Date getLimitEndTime() {
+        return limitEndTime;
+    }
+
+    public void setLimitEndTime(Date limitEndTime) {
+        this.limitEndTime = limitEndTime;
+    }
+
+    public Integer getFrameTextContentId() {
+        return frameTextContentId;
+    }
+
+    public void setFrameTextContentId(Integer frameTextContentId) {
+        this.frameTextContentId = frameTextContentId;
+    }
+
+    public Integer getCreateUser() {
+        return createUser;
+    }
+
+    public void setCreateUser(Integer createUser) {
+        this.createUser = createUser;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Boolean getDeleted() {
+        return deleted;
+    }
+
+    public void setDeleted(Boolean deleted) {
+        this.deleted = deleted;
+    }
+
+    public Integer getTaskExamId() {
+        return taskExamId;
+    }
+
+    public void setTaskExamId(Integer taskExamId) {
+        this.taskExamId = taskExamId;
+    }
+}

+ 195 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/ExamPaperAnswer.java

@@ -0,0 +1,195 @@
+package vip.xiaonuo.exam.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class ExamPaperAnswer implements Serializable {
+
+    private static final long serialVersionUID = -2143539181805283910L;
+
+    private Integer id;
+
+    private Integer examPaperId;
+
+    /**
+     * 试卷名称
+     */
+    private String paperName;
+
+    /**
+     * 试卷类型( 1固定试卷 4.时段试卷 6.任务试卷)
+     */
+    private Integer paperType;
+
+    /**
+     * 学科
+     */
+    private Integer subjectId;
+
+    /**
+     * 系统判定得分
+     */
+    private Integer systemScore;
+
+    /**
+     * 最终得分(千分制)
+     */
+    private Integer userScore;
+
+    /**
+     * 试卷总分
+     */
+    private Integer paperScore;
+
+    /**
+     * 做对题目数量
+     */
+    private Integer questionCorrect;
+
+    /**
+     * 题目总数量
+     */
+    private Integer questionCount;
+
+    /**
+     * 做题时间(秒)
+     */
+    private Integer doTime;
+
+    /**
+     * 试卷状态(1待判分 2完成)
+     */
+    private Integer status;
+
+    /**
+     * 学生
+     */
+    private Integer createUser;
+
+    /**
+     * 提交时间
+     */
+    private Date createTime;
+
+    private Integer taskExamId;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getExamPaperId() {
+        return examPaperId;
+    }
+
+    public void setExamPaperId(Integer examPaperId) {
+        this.examPaperId = examPaperId;
+    }
+
+    public String getPaperName() {
+        return paperName;
+    }
+
+    public void setPaperName(String paperName) {
+        this.paperName = paperName == null ? null : paperName.trim();
+    }
+
+    public Integer getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(Integer paperType) {
+        this.paperType = paperType;
+    }
+
+    public Integer getSubjectId() {
+        return subjectId;
+    }
+
+    public void setSubjectId(Integer subjectId) {
+        this.subjectId = subjectId;
+    }
+
+    public Integer getSystemScore() {
+        return systemScore;
+    }
+
+    public void setSystemScore(Integer systemScore) {
+        this.systemScore = systemScore;
+    }
+
+    public Integer getUserScore() {
+        return userScore;
+    }
+
+    public void setUserScore(Integer userScore) {
+        this.userScore = userScore;
+    }
+
+    public Integer getPaperScore() {
+        return paperScore;
+    }
+
+    public void setPaperScore(Integer paperScore) {
+        this.paperScore = paperScore;
+    }
+
+    public Integer getQuestionCorrect() {
+        return questionCorrect;
+    }
+
+    public void setQuestionCorrect(Integer questionCorrect) {
+        this.questionCorrect = questionCorrect;
+    }
+
+    public Integer getQuestionCount() {
+        return questionCount;
+    }
+
+    public void setQuestionCount(Integer questionCount) {
+        this.questionCount = questionCount;
+    }
+
+    public Integer getDoTime() {
+        return doTime;
+    }
+
+    public void setDoTime(Integer doTime) {
+        this.doTime = doTime;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Integer getCreateUser() {
+        return createUser;
+    }
+
+    public void setCreateUser(Integer createUser) {
+        this.createUser = createUser;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Integer getTaskExamId() {
+        return taskExamId;
+    }
+
+    public void setTaskExamId(Integer taskExamId) {
+        this.taskExamId = taskExamId;
+    }
+}

+ 34 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/ExamPaperAnswerInfo.java

@@ -0,0 +1,34 @@
+package vip.xiaonuo.exam.domain;
+
+
+import java.util.List;
+
+public class ExamPaperAnswerInfo {
+    public ExamPaper examPaper;
+    public ExamPaperAnswer examPaperAnswer;
+    public List<ExamPaperQuestionCustomerAnswer> examPaperQuestionCustomerAnswers;
+
+    public ExamPaper getExamPaper() {
+        return examPaper;
+    }
+
+    public void setExamPaper(ExamPaper examPaper) {
+        this.examPaper = examPaper;
+    }
+
+    public ExamPaperAnswer getExamPaperAnswer() {
+        return examPaperAnswer;
+    }
+
+    public void setExamPaperAnswer(ExamPaperAnswer examPaperAnswer) {
+        this.examPaperAnswer = examPaperAnswer;
+    }
+
+    public List<ExamPaperQuestionCustomerAnswer> getExamPaperQuestionCustomerAnswers() {
+        return examPaperQuestionCustomerAnswers;
+    }
+
+    public void setExamPaperQuestionCustomerAnswers(List<ExamPaperQuestionCustomerAnswer> examPaperQuestionCustomerAnswers) {
+        this.examPaperQuestionCustomerAnswers = examPaperQuestionCustomerAnswers;
+    }
+}

+ 195 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/ExamPaperQuestionCustomerAnswer.java

@@ -0,0 +1,195 @@
+package vip.xiaonuo.exam.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class ExamPaperQuestionCustomerAnswer implements Serializable {
+
+    private static final long serialVersionUID = 3389482731220342366L;
+
+    private Integer id;
+
+    /**
+     * 题目Id
+     */
+    private Integer questionId;
+
+    /**
+     * 试卷Id
+     */
+    private Integer examPaperId;
+
+    /**
+     * 答案Id
+     */
+    private Integer examPaperAnswerId;
+
+    /**
+     * 题型
+     */
+    private Integer questionType;
+
+    /**
+     * 学科
+     */
+    private Integer subjectId;
+
+    /**
+     * 得分
+     */
+    private Integer customerScore;
+
+    /**
+     * 题目原始分数
+     */
+    private Integer questionScore;
+
+    /**
+     * 问题内容
+     */
+    private Integer questionTextContentId;
+
+    /**
+     * 做题答案
+     */
+    private String answer;
+
+    /**
+     * 做题内容
+     */
+    private Integer textContentId;
+
+    /**
+     * 是否正确
+     */
+    private Boolean doRight;
+
+    /**
+     * 做题人
+     */
+    private Integer createUser;
+
+    private Date createTime;
+
+    private Integer itemOrder;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getQuestionId() {
+        return questionId;
+    }
+
+    public void setQuestionId(Integer questionId) {
+        this.questionId = questionId;
+    }
+
+    public Integer getExamPaperId() {
+        return examPaperId;
+    }
+
+    public void setExamPaperId(Integer examPaperId) {
+        this.examPaperId = examPaperId;
+    }
+
+    public Integer getExamPaperAnswerId() {
+        return examPaperAnswerId;
+    }
+
+    public void setExamPaperAnswerId(Integer examPaperAnswerId) {
+        this.examPaperAnswerId = examPaperAnswerId;
+    }
+
+    public Integer getQuestionType() {
+        return questionType;
+    }
+
+    public void setQuestionType(Integer questionType) {
+        this.questionType = questionType;
+    }
+
+    public Integer getSubjectId() {
+        return subjectId;
+    }
+
+    public void setSubjectId(Integer subjectId) {
+        this.subjectId = subjectId;
+    }
+
+    public Integer getCustomerScore() {
+        return customerScore;
+    }
+
+    public void setCustomerScore(Integer customerScore) {
+        this.customerScore = customerScore;
+    }
+
+    public Integer getQuestionScore() {
+        return questionScore;
+    }
+
+    public void setQuestionScore(Integer questionScore) {
+        this.questionScore = questionScore;
+    }
+
+    public Integer getQuestionTextContentId() {
+        return questionTextContentId;
+    }
+
+    public void setQuestionTextContentId(Integer questionTextContentId) {
+        this.questionTextContentId = questionTextContentId;
+    }
+
+    public String getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(String answer) {
+        this.answer = answer == null ? null : answer.trim();
+    }
+
+    public Integer getTextContentId() {
+        return textContentId;
+    }
+
+    public void setTextContentId(Integer textContentId) {
+        this.textContentId = textContentId;
+    }
+
+    public Boolean getDoRight() {
+        return doRight;
+    }
+
+    public void setDoRight(Boolean doRight) {
+        this.doRight = doRight;
+    }
+
+    public Integer getCreateUser() {
+        return createUser;
+    }
+
+    public void setCreateUser(Integer createUser) {
+        this.createUser = createUser;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Integer getItemOrder() {
+        return itemOrder;
+    }
+
+    public void setItemOrder(Integer itemOrder) {
+        this.itemOrder = itemOrder;
+    }
+}

+ 123 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/Message.java

@@ -0,0 +1,123 @@
+package vip.xiaonuo.exam.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class Message implements Serializable {
+
+    private static final long serialVersionUID = -3510265139403747341L;
+
+    private Integer id;
+
+    /**
+     * 标题
+     */
+    private String title;
+
+    /**
+     * 内容
+     */
+    private String content;
+
+    private Date createTime;
+
+    /**
+     * 发送者用户ID
+     */
+    private Integer sendUserId;
+
+    /**
+     * 发送者用户名
+     */
+    private String sendUserName;
+
+    /**
+     * 发送者真实姓名
+     */
+    private String sendRealName;
+
+    /**
+     * 接收人数
+     */
+    private Integer receiveUserCount;
+
+    /**
+     * 已读人数
+     */
+    private Integer readCount;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title == null ? null : title.trim();
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content == null ? null : content.trim();
+    }
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Integer getSendUserId() {
+        return sendUserId;
+    }
+
+    public void setSendUserId(Integer sendUserId) {
+        this.sendUserId = sendUserId;
+    }
+
+    public String getSendUserName() {
+        return sendUserName;
+    }
+
+    public void setSendUserName(String sendUserName) {
+        this.sendUserName = sendUserName == null ? null : sendUserName.trim();
+    }
+
+    public String getSendRealName() {
+        return sendRealName;
+    }
+
+    public void setSendRealName(String sendRealName) {
+        this.sendRealName = sendRealName == null ? null : sendRealName.trim();
+    }
+
+    public Integer getReceiveUserCount() {
+        return receiveUserCount;
+    }
+
+    public void setReceiveUserCount(Integer receiveUserCount) {
+        this.receiveUserCount = receiveUserCount;
+    }
+
+    public Integer getReadCount() {
+        return readCount;
+    }
+
+    public void setReadCount(Integer readCount) {
+        this.readCount = readCount;
+    }
+}

+ 107 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/MessageUser.java

@@ -0,0 +1,107 @@
+package vip.xiaonuo.exam.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class MessageUser implements Serializable {
+
+    private static final long serialVersionUID = -4042932811802896498L;
+
+    private Integer id;
+
+    /**
+     * 消息内容ID
+     */
+    private Integer messageId;
+
+    /**
+     * 接收人ID
+     */
+    private Integer receiveUserId;
+
+    /**
+     * 接收人用户名
+     */
+    private String receiveUserName;
+
+    /**
+     * 接收人真实姓名
+     */
+    private String receiveRealName;
+
+    /**
+     * 是否已读
+     */
+    private Boolean readed;
+
+    private Date createTime;
+
+    /**
+     * 阅读时间
+     */
+    private Date readTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getMessageId() {
+        return messageId;
+    }
+
+    public void setMessageId(Integer messageId) {
+        this.messageId = messageId;
+    }
+
+    public Integer getReceiveUserId() {
+        return receiveUserId;
+    }
+
+    public void setReceiveUserId(Integer receiveUserId) {
+        this.receiveUserId = receiveUserId;
+    }
+
+    public String getReceiveUserName() {
+        return receiveUserName;
+    }
+
+    public void setReceiveUserName(String receiveUserName) {
+        this.receiveUserName = receiveUserName == null ? null : receiveUserName.trim();
+    }
+
+    public String getReceiveRealName() {
+        return receiveRealName;
+    }
+
+    public void setReceiveRealName(String receiveRealName) {
+        this.receiveRealName = receiveRealName == null ? null : receiveRealName.trim();
+    }
+
+    public Boolean getReaded() {
+        return readed;
+    }
+
+    public void setReaded(Boolean readed) {
+        this.readed = readed;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getReadTime() {
+        return readTime;
+    }
+
+    public void setReadTime(Date readTime) {
+        this.readTime = readTime;
+    }
+}

+ 174 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/Question.java

@@ -0,0 +1,174 @@
+package vip.xiaonuo.exam.domain;
+
+import vip.xiaonuo.exam.domain.enums.QuestionTypeEnum;
+import vip.xiaonuo.exam.utility.ExamUtil;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+public class Question implements Serializable {
+
+    private static final long serialVersionUID = 8826266720383164363L;
+
+    private Integer id;
+
+    /**
+     * 	1.单选题 2.多选题 3.判断题 4.填空题 5.简答题
+     */
+    private Integer questionType;
+
+    /**
+     * 学科
+     */
+    private Integer subjectId;
+
+    /**
+     * 题目总分(千分制)
+     */
+    private Integer score;
+
+    /**
+     * 级别
+     */
+    private Integer gradeLevel;
+
+    /**
+     * 题目难度
+     */
+    private Integer difficult;
+
+    /**
+     * 正确答案
+     */
+    private String correct;
+
+    /**
+     * 题目 填空、 题干、解析、答案等信息
+     */
+    private Integer infoTextContentId;
+
+    /**
+     * 创建人
+     */
+    private Integer createUser;
+
+    /**
+     * 1.正常
+     */
+    private Integer status;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    private Boolean deleted;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getQuestionType() {
+        return questionType;
+    }
+
+    public void setQuestionType(Integer questionType) {
+        this.questionType = questionType;
+    }
+
+    public Integer getSubjectId() {
+        return subjectId;
+    }
+
+    public void setSubjectId(Integer subjectId) {
+        this.subjectId = subjectId;
+    }
+
+    public Integer getScore() {
+        return score;
+    }
+
+    public void setScore(Integer score) {
+        this.score = score;
+    }
+
+    public Integer getGradeLevel() {
+        return gradeLevel;
+    }
+
+    public void setGradeLevel(Integer gradeLevel) {
+        this.gradeLevel = gradeLevel;
+    }
+
+    public Integer getDifficult() {
+        return difficult;
+    }
+
+    public void setDifficult(Integer difficult) {
+        this.difficult = difficult;
+    }
+
+    public String getCorrect() {
+        return correct;
+    }
+
+    public void setCorrect(String correct) {
+        this.correct = correct == null ? null : correct.trim();
+    }
+
+    public Integer getInfoTextContentId() {
+        return infoTextContentId;
+    }
+
+    public void setInfoTextContentId(Integer infoTextContentId) {
+        this.infoTextContentId = infoTextContentId;
+    }
+
+    public Integer getCreateUser() {
+        return createUser;
+    }
+
+    public void setCreateUser(Integer createUser) {
+        this.createUser = createUser;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Boolean getDeleted() {
+        return deleted;
+    }
+
+    public void setDeleted(Boolean deleted) {
+        this.deleted = deleted;
+    }
+
+
+    public void setCorrectFromVM(String correct, List<String> correctArray) {
+        int qType = this.getQuestionType();
+        if (qType == QuestionTypeEnum.MultipleChoice.getCode()) {
+            String correctJoin = ExamUtil.contentToString(correctArray);
+            this.setCorrect(correctJoin);
+        } else {
+            this.setCorrect(correct);
+        }
+    }
+}

+ 80 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/Subject.java

@@ -0,0 +1,80 @@
+package vip.xiaonuo.exam.domain;
+
+import java.io.Serializable;
+
+public class Subject implements Serializable {
+
+    private static final long serialVersionUID = 8058095034457106501L;
+
+    private Integer id;
+
+    /**
+     * 语文 数学 英语 等
+     */
+    private String name;
+
+    /**
+     * 年级 (1-12) 小学 初中
+     */
+    private Integer level;
+
+    /**
+     * 一年级、二年级等
+     */
+    private String levelName;
+
+    /**
+     * 排序
+     */
+    private Integer itemOrder;
+
+    private Boolean deleted;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name == null ? null : name.trim();
+    }
+
+    public Integer getLevel() {
+        return level;
+    }
+
+    public void setLevel(Integer level) {
+        this.level = level;
+    }
+
+    public String getLevelName() {
+        return levelName;
+    }
+
+    public void setLevelName(String levelName) {
+        this.levelName = levelName == null ? null : levelName.trim();
+    }
+
+    public Integer getItemOrder() {
+        return itemOrder;
+    }
+
+    public void setItemOrder(Integer itemOrder) {
+        this.itemOrder = itemOrder;
+    }
+
+    public Boolean getDeleted() {
+        return deleted;
+    }
+
+    public void setDeleted(Boolean deleted) {
+        this.deleted = deleted;
+    }
+}

+ 107 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/TaskExam.java

@@ -0,0 +1,107 @@
+package vip.xiaonuo.exam.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class TaskExam implements Serializable {
+
+    private static final long serialVersionUID = -7014704644631536195L;
+
+    private Integer id;
+
+    /**
+     * 任务标题
+     */
+    private String title;
+
+    /**
+     * 年级
+     */
+    private Integer gradeLevel;
+
+    /**
+     * 任务框架 内容为JSON
+     */
+    private Integer frameTextContentId;
+
+    /**
+     * 创建者
+     */
+    private Integer createUser;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    private Boolean deleted;
+
+    /**
+     * 创建人用户名
+     */
+    private String createUserName;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title == null ? null : title.trim();
+    }
+
+    public Integer getGradeLevel() {
+        return gradeLevel;
+    }
+
+    public void setGradeLevel(Integer gradeLevel) {
+        this.gradeLevel = gradeLevel;
+    }
+
+    public Integer getFrameTextContentId() {
+        return frameTextContentId;
+    }
+
+    public void setFrameTextContentId(Integer frameTextContentId) {
+        this.frameTextContentId = frameTextContentId;
+    }
+
+    public Integer getCreateUser() {
+        return createUser;
+    }
+
+    public void setCreateUser(Integer createUser) {
+        this.createUser = createUser;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Boolean getDeleted() {
+        return deleted;
+    }
+
+    public void setDeleted(Boolean deleted) {
+        this.deleted = deleted;
+    }
+
+    public String getCreateUserName() {
+        return createUserName;
+    }
+
+    public void setCreateUserName(String createUserName) {
+        this.createUserName = createUserName == null ? null : createUserName.trim();
+    }
+}

+ 71 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/TaskExamCustomerAnswer.java

@@ -0,0 +1,71 @@
+package vip.xiaonuo.exam.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class TaskExamCustomerAnswer implements Serializable {
+
+    private static final long serialVersionUID = -556842372977600137L;
+
+    private Integer id;
+
+    /**
+     * 任务Id
+     */
+    private Integer taskExamId;
+
+    /**
+     * 创建者
+     */
+    private Integer createUser;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 任务完成情况(Json)
+     */
+    private Integer textContentId;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getTaskExamId() {
+        return taskExamId;
+    }
+
+    public void setTaskExamId(Integer taskExamId) {
+        this.taskExamId = taskExamId;
+    }
+
+    public Integer getCreateUser() {
+        return createUser;
+    }
+
+    public void setCreateUser(Integer createUser) {
+        this.createUser = createUser;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Integer getTextContentId() {
+        return textContentId;
+    }
+
+    public void setTextContentId(Integer textContentId) {
+        this.textContentId = textContentId;
+    }
+}

+ 54 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/TextContent.java

@@ -0,0 +1,54 @@
+package vip.xiaonuo.exam.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class TextContent implements Serializable {
+
+    private static final long serialVersionUID = -1279530310964668131L;
+
+    public TextContent(){
+
+    }
+
+    public TextContent(String content, Date createTime) {
+        this.content = content;
+        this.createTime = createTime;
+    }
+
+    private Integer id;
+
+    /**
+     * 内容(Json)
+     */
+    private String content;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content == null ? null : content.trim();
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+}

+ 216 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/User.java

@@ -0,0 +1,216 @@
+package vip.xiaonuo.exam.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class User implements Serializable {
+
+    private static final long serialVersionUID = -7797183521247423117L;
+
+    private Integer id;
+
+    private String userUuid;
+
+    /**
+     * 用户名
+     */
+    private String userName;
+
+    private String password;
+
+    /**
+     * 真实姓名
+     */
+    private String realName;
+
+    private Integer age;
+
+    /**
+     * 1.男 2女
+     */
+    private Integer sex;
+
+    private Date birthDay;
+
+    /**
+     * 学生年级(1-12)
+     */
+    private Integer userLevel;
+
+    private String phone;
+
+    /**
+     * 1.学生  3.管理员
+     */
+    private Integer role;
+
+    /**
+     * 1.启用 2禁用
+     */
+    private Integer status;
+
+    /**
+     * 头像地址
+     */
+    private String imagePath;
+
+    private Date createTime;
+
+    private Date modifyTime;
+
+    private Date lastActiveTime;
+
+    /**
+     * 是否删除
+     */
+    private Boolean deleted;
+
+    /**
+     * 微信openId
+     */
+    private String wxOpenId;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getUserUuid() {
+        return userUuid;
+    }
+
+    public void setUserUuid(String userUuid) {
+        this.userUuid = userUuid == null ? null : userUuid.trim();
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName == null ? null : userName.trim();
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password == null ? null : password.trim();
+    }
+
+    public String getRealName() {
+        return realName;
+    }
+
+    public void setRealName(String realName) {
+        this.realName = realName == null ? null : realName.trim();
+    }
+
+    public Integer getAge() {
+        return age;
+    }
+
+    public void setAge(Integer age) {
+        this.age = age;
+    }
+
+    public Integer getSex() {
+        return sex;
+    }
+
+    public void setSex(Integer sex) {
+        this.sex = sex;
+    }
+
+    public Date getBirthDay() {
+        return birthDay;
+    }
+
+    public void setBirthDay(Date birthDay) {
+        this.birthDay = birthDay;
+    }
+
+    public Integer getUserLevel() {
+        return userLevel;
+    }
+
+    public void setUserLevel(Integer userLevel) {
+        this.userLevel = userLevel;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone == null ? null : phone.trim();
+    }
+
+    public Integer getRole() {
+        return role;
+    }
+
+    public void setRole(Integer role) {
+        this.role = role;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public String getImagePath() {
+        return imagePath;
+    }
+
+    public void setImagePath(String imagePath) {
+        this.imagePath = imagePath == null ? null : imagePath.trim();
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getModifyTime() {
+        return modifyTime;
+    }
+
+    public void setModifyTime(Date modifyTime) {
+        this.modifyTime = modifyTime;
+    }
+
+    public Date getLastActiveTime() {
+        return lastActiveTime;
+    }
+
+    public void setLastActiveTime(Date lastActiveTime) {
+        this.lastActiveTime = lastActiveTime;
+    }
+
+    public Boolean getDeleted() {
+        return deleted;
+    }
+
+    public void setDeleted(Boolean deleted) {
+        this.deleted = deleted;
+    }
+
+    public String getWxOpenId() {
+        return wxOpenId;
+    }
+
+    public void setWxOpenId(String wxOpenId) {
+        this.wxOpenId = wxOpenId == null ? null : wxOpenId.trim();
+    }
+}

+ 96 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/UserEventLog.java

@@ -0,0 +1,96 @@
+package vip.xiaonuo.exam.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class UserEventLog implements Serializable {
+
+    private static final long serialVersionUID = -3951198127152024633L;
+
+
+    public UserEventLog() {
+
+    }
+
+    public UserEventLog(Integer userId, String userName, String realName, Date createTime) {
+        this.userId = userId;
+        this.userName = userName;
+        this.realName = realName;
+        this.createTime = createTime;
+    }
+
+    private Integer id;
+
+    /**
+     * 用户id
+     */
+    private Integer userId;
+
+    /**
+     * 用户名
+     */
+    private String userName;
+
+    /**
+     * 真实姓名
+     */
+    private String realName;
+
+    /**
+     * 内容
+     */
+    private String content;
+
+    /**
+     * 时间
+     */
+    private Date createTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName == null ? null : userName.trim();
+    }
+
+    public String getRealName() {
+        return realName;
+    }
+
+    public void setRealName(String realName) {
+        this.realName = realName == null ? null : realName.trim();
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content == null ? null : content.trim();
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+}

+ 97 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/UserToken.java

@@ -0,0 +1,97 @@
+package vip.xiaonuo.exam.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class UserToken implements Serializable {
+
+    private static final long serialVersionUID = -2414443061696200360L;
+
+    private Integer id;
+
+    /**
+     * 用户token
+     */
+    private String token;
+
+    /**
+     * 用户Id
+     */
+    private Integer userId;
+
+    /**
+     * 微信小程序openId
+     */
+    private String wxOpenId;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 结束时间
+     */
+    private Date endTime;
+
+    /**
+     * 用户名
+     */
+    private String userName;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token == null ? null : token.trim();
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public String getWxOpenId() {
+        return wxOpenId;
+    }
+
+    public void setWxOpenId(String wxOpenId) {
+        this.wxOpenId = wxOpenId == null ? null : wxOpenId.trim();
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName == null ? null : userName.trim();
+    }
+}

+ 49 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/enums/ExamPaperAnswerStatusEnum.java

@@ -0,0 +1,49 @@
+package vip.xiaonuo.exam.domain.enums;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum ExamPaperAnswerStatusEnum {
+
+    WaitJudge(1, "待批改"),
+    Complete(2, "完成");
+
+    int code;
+    String name;
+
+    ExamPaperAnswerStatusEnum(int code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+
+    private static final Map<Integer, ExamPaperAnswerStatusEnum> keyMap = new HashMap<>();
+
+    static {
+        for (ExamPaperAnswerStatusEnum item : ExamPaperAnswerStatusEnum.values()) {
+            keyMap.put(item.getCode(), item);
+        }
+    }
+
+    public static ExamPaperAnswerStatusEnum fromCode(Integer code) {
+        return keyMap.get(code);
+    }
+
+}

+ 51 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/enums/ExamPaperTypeEnum.java

@@ -0,0 +1,51 @@
+package vip.xiaonuo.exam.domain.enums;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum ExamPaperTypeEnum {
+
+    Fixed(1, "固定试卷"),
+    TimeLimit(4, "时段试卷"),
+    Task(6, "任务试卷");
+
+    int code;
+    String name;
+
+    ExamPaperTypeEnum(int code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+
+    private static final Map<Integer, ExamPaperTypeEnum> keyMap = new HashMap<>();
+
+    static {
+        for (ExamPaperTypeEnum item : ExamPaperTypeEnum.values()) {
+            keyMap.put(item.getCode(), item);
+        }
+    }
+
+    public static ExamPaperTypeEnum fromCode(Integer code) {
+        return keyMap.get(code);
+    }
+
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+
+}

+ 34 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/enums/QuestionStatusEnum.java

@@ -0,0 +1,34 @@
+package vip.xiaonuo.exam.domain.enums;
+
+public enum QuestionStatusEnum {
+
+    OK(1, "正常"),
+    Publish(2, "发布");
+
+    int code;
+    String name;
+
+    QuestionStatusEnum(int code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+
+}

+ 63 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/enums/QuestionTypeEnum.java

@@ -0,0 +1,63 @@
+package vip.xiaonuo.exam.domain.enums;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum QuestionTypeEnum {
+
+    SingleChoice(1, "单选题"),
+    MultipleChoice(2, "多选题"),
+    TrueFalse(3, "判断题"),
+    GapFilling(4, "填空题"),
+    ShortAnswer(5, "简答题");
+
+    int code;
+    String name;
+
+    QuestionTypeEnum(int code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+
+    private static final Map<Integer, QuestionTypeEnum> keyMap = new HashMap<>();
+
+    static {
+        for (QuestionTypeEnum item : QuestionTypeEnum.values()) {
+            keyMap.put(item.getCode(), item);
+        }
+    }
+
+    public static QuestionTypeEnum fromCode(Integer code) {
+        return keyMap.get(code);
+    }
+
+    public static boolean needSaveTextContent(Integer code) {
+        QuestionTypeEnum questionTypeEnum = QuestionTypeEnum.fromCode(code);
+        switch (questionTypeEnum) {
+            case GapFilling:
+            case ShortAnswer:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+
+}

+ 52 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/enums/RoleEnum.java

@@ -0,0 +1,52 @@
+package vip.xiaonuo.exam.domain.enums;
+
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum RoleEnum {
+
+    STUDENT(1, "STUDENT"),
+    ADMIN(3, "ADMIN");
+
+    int code;
+    String name;
+
+    RoleEnum(int code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+    private static final Map<Integer, RoleEnum> keyMap = new HashMap<>();
+
+    static {
+        for (RoleEnum item : RoleEnum.values()) {
+            keyMap.put(item.getCode(), item);
+        }
+    }
+
+    public static RoleEnum fromCode(Integer code) {
+        return keyMap.get(code);
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getRoleName() {
+        return "ROLE_" + name;
+    }
+
+}

+ 48 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/enums/UserStatusEnum.java

@@ -0,0 +1,48 @@
+package vip.xiaonuo.exam.domain.enums;
+
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum UserStatusEnum {
+
+    Enable(1, "启用"),
+    Disable(2, "禁用");
+
+    int code;
+    String name;
+
+    UserStatusEnum(int code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+    private static final Map<Integer, UserStatusEnum> keyMap = new HashMap<>();
+
+    static {
+        for (UserStatusEnum item : UserStatusEnum.values()) {
+            keyMap.put(item.getCode(), item);
+        }
+    }
+
+    public static UserStatusEnum fromCode(Integer code) {
+        return keyMap.get(code);
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+}

+ 22 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/exam/ExamPaperQuestionItemObject.java

@@ -0,0 +1,22 @@
+package vip.xiaonuo.exam.domain.exam;
+
+public class ExamPaperQuestionItemObject {
+    private Integer id;
+    private Integer itemOrder;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getItemOrder() {
+        return itemOrder;
+    }
+
+    public void setItemOrder(Integer itemOrder) {
+        this.itemOrder = itemOrder;
+    }
+}

+ 27 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/exam/ExamPaperTitleItemObject.java

@@ -0,0 +1,27 @@
+package vip.xiaonuo.exam.domain.exam;
+
+
+import java.util.List;
+
+public class ExamPaperTitleItemObject {
+
+    private String name;
+
+    private List<ExamPaperQuestionItemObject> questionItems;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public List<ExamPaperQuestionItemObject> getQuestionItems() {
+        return questionItems;
+    }
+
+    public void setQuestionItems(List<ExamPaperQuestionItemObject> questionItems) {
+        this.questionItems = questionItems;
+    }
+}

+ 32 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/other/ExamPaperAnswerUpdate.java

@@ -0,0 +1,32 @@
+package vip.xiaonuo.exam.domain.other;
+
+
+public class ExamPaperAnswerUpdate {
+    private Integer id;
+    private Integer customerScore;
+    private Boolean doRight;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getCustomerScore() {
+        return customerScore;
+    }
+
+    public void setCustomerScore(Integer customerScore) {
+        this.customerScore = customerScore;
+    }
+
+    public Boolean getDoRight() {
+        return doRight;
+    }
+
+    public void setDoRight(Boolean doRight) {
+        this.doRight = doRight;
+    }
+}

+ 24 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/other/KeyValue.java

@@ -0,0 +1,24 @@
+package vip.xiaonuo.exam.domain.other;
+
+
+public class KeyValue {
+
+    private String name;
+    private Integer value;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Integer getValue() {
+        return value;
+    }
+
+    public void setValue(Integer value) {
+        this.value = value;
+    }
+}

+ 45 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/question/QuestionItemObject.java

@@ -0,0 +1,45 @@
+package vip.xiaonuo.exam.domain.question;
+
+
+public class QuestionItemObject {
+
+    private String prefix;
+
+    private String content;
+
+    private Integer score;
+
+    private String itemUuid;
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public void setPrefix(String prefix) {
+        this.prefix = prefix;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Integer getScore() {
+        return score;
+    }
+
+    public void setScore(Integer score) {
+        this.score = score;
+    }
+
+    public String getItemUuid() {
+        return itemUuid;
+    }
+
+    public void setItemUuid(String itemUuid) {
+        this.itemUuid = itemUuid;
+    }
+}

+ 48 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/question/QuestionObject.java

@@ -0,0 +1,48 @@
+package vip.xiaonuo.exam.domain.question;
+
+
+
+import java.util.List;
+
+public class QuestionObject {
+
+    private String titleContent;
+
+    private String analyze;
+
+    private List<QuestionItemObject> questionItemObjects;
+
+    private String correct;
+
+    public String getTitleContent() {
+        return titleContent;
+    }
+
+    public void setTitleContent(String titleContent) {
+        this.titleContent = titleContent;
+    }
+
+    public String getAnalyze() {
+        return analyze;
+    }
+
+    public void setAnalyze(String analyze) {
+        this.analyze = analyze;
+    }
+
+    public List<QuestionItemObject> getQuestionItemObjects() {
+        return questionItemObjects;
+    }
+
+    public void setQuestionItemObjects(List<QuestionItemObject> questionItemObjects) {
+        this.questionItemObjects = questionItemObjects;
+    }
+
+    public String getCorrect() {
+        return correct;
+    }
+
+    public void setCorrect(String correct) {
+        this.correct = correct;
+    }
+}

+ 42 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/task/TaskItemAnswerObject.java

@@ -0,0 +1,42 @@
+package vip.xiaonuo.exam.domain.task;
+
+
+public class TaskItemAnswerObject {
+    private Integer examPaperId;
+    private Integer examPaperAnswerId;
+    private Integer status;
+
+    public TaskItemAnswerObject(){
+
+    }
+
+    public TaskItemAnswerObject(Integer examPaperId, Integer examPaperAnswerId, Integer status) {
+        this.examPaperId = examPaperId;
+        this.examPaperAnswerId = examPaperAnswerId;
+        this.status = status;
+    }
+
+    public Integer getExamPaperId() {
+        return examPaperId;
+    }
+
+    public void setExamPaperId(Integer examPaperId) {
+        this.examPaperId = examPaperId;
+    }
+
+    public Integer getExamPaperAnswerId() {
+        return examPaperAnswerId;
+    }
+
+    public void setExamPaperAnswerId(Integer examPaperAnswerId) {
+        this.examPaperAnswerId = examPaperAnswerId;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+}

+ 32 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/domain/task/TaskItemObject.java

@@ -0,0 +1,32 @@
+package vip.xiaonuo.exam.domain.task;
+
+
+public class TaskItemObject {
+    private Integer examPaperId;
+    private String examPaperName;
+    private Integer itemOrder;
+
+    public Integer getExamPaperId() {
+        return examPaperId;
+    }
+
+    public void setExamPaperId(Integer examPaperId) {
+        this.examPaperId = examPaperId;
+    }
+
+    public String getExamPaperName() {
+        return examPaperName;
+    }
+
+    public void setExamPaperName(String examPaperName) {
+        this.examPaperName = examPaperName;
+    }
+
+    public Integer getItemOrder() {
+        return itemOrder;
+    }
+
+    public void setItemOrder(Integer itemOrder) {
+        this.itemOrder = itemOrder;
+    }
+}

+ 37 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/event/CalculateExamPaperAnswerCompleteEvent.java

@@ -0,0 +1,37 @@
+package vip.xiaonuo.exam.event;
+
+import vip.xiaonuo.exam.domain.ExamPaperAnswerInfo;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @version 3.3.0
+ * @description: The type Calculate exam paper answer complete event.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/5/25 10:45
+ */
+public class CalculateExamPaperAnswerCompleteEvent extends ApplicationEvent {
+
+
+    private final ExamPaperAnswerInfo examPaperAnswerInfo;
+
+
+    /**
+     * Instantiates a new Calculate exam paper answer complete event.
+     *
+     * @param examPaperAnswerInfo the exam paper answer info
+     */
+    public CalculateExamPaperAnswerCompleteEvent(final ExamPaperAnswerInfo examPaperAnswerInfo) {
+        super(examPaperAnswerInfo);
+        this.examPaperAnswerInfo = examPaperAnswerInfo;
+    }
+
+    /**
+     * Gets exam paper answer info.
+     *
+     * @return the exam paper answer info
+     */
+    public ExamPaperAnswerInfo getExamPaperAnswerInfo() {
+        return examPaperAnswerInfo;
+    }
+
+}

+ 37 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/event/OnRegistrationCompleteEvent.java

@@ -0,0 +1,37 @@
+package vip.xiaonuo.exam.event;
+
+import vip.xiaonuo.exam.domain.User;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @version 3.5.0
+ * @description: The type On registration complete event.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+public class OnRegistrationCompleteEvent extends ApplicationEvent {
+
+
+    private final User user;
+
+
+    /**
+     * Instantiates a new On registration complete event.
+     *
+     * @param user the user
+     */
+    public OnRegistrationCompleteEvent(final User user) {
+        super(user);
+        this.user = user;
+    }
+
+    /**
+     * Gets user.
+     *
+     * @return the user
+     */
+    public User getUser() {
+        return user;
+    }
+
+}

+ 34 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/event/UserEvent.java

@@ -0,0 +1,34 @@
+package vip.xiaonuo.exam.event;
+
+import vip.xiaonuo.exam.domain.UserEventLog;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @version 3.5.0
+ * @description: The type User event.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+public class UserEvent extends ApplicationEvent {
+
+    private final UserEventLog userEventLog;
+
+    /**
+     * Instantiates a new User event.
+     *
+     * @param userEventLog the user event log
+     */
+    public UserEvent(final UserEventLog userEventLog) {
+        super(userEventLog);
+        this.userEventLog = userEventLog;
+    }
+
+    /**
+     * Gets user event log.
+     *
+     * @return the user event log
+     */
+    public UserEventLog getUserEventLog() {
+        return userEventLog;
+    }
+}

+ 125 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/exception/BusinessException.java

@@ -0,0 +1,125 @@
+package vip.xiaonuo.exam.exception;
+
+/**
+ * @version 3.5.0
+ * @description: The type Business exception.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+public class BusinessException extends RuntimeException {
+
+    /**
+     * The constant UNKNOWN_EXCEPTION.
+     */
+    public static final int UNKNOWN_EXCEPTION = 0;
+
+    private int code;
+
+    /**
+     * Instantiates a new Business exception.
+     */
+    public BusinessException() {
+        super();
+    }
+
+    /**
+     * Instantiates a new Business exception.
+     *
+     * @param message the message
+     * @param cause   the cause
+     */
+    public BusinessException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Instantiates a new Business exception.
+     *
+     * @param message the message
+     */
+    public BusinessException(String message) {
+        super(message);
+    }
+
+    /**
+     * Instantiates a new Business exception.
+     *
+     * @param cause the cause
+     */
+    public BusinessException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Instantiates a new Business exception.
+     *
+     * @param code the code
+     */
+    public BusinessException(int code) {
+        super();
+        this.code = code;
+    }
+
+    /**
+     * Instantiates a new Business exception.
+     *
+     * @param code    the code
+     * @param message the message
+     * @param cause   the cause
+     */
+    public BusinessException(int code, String message, Throwable cause) {
+        super(message, cause);
+        this.code = code;
+    }
+
+    /**
+     * Instantiates a new Business exception.
+     *
+     * @param code    the code
+     * @param message the message
+     */
+    public BusinessException(int code, String message) {
+        super(message);
+        this.code = code;
+    }
+
+    /**
+     * Instantiates a new Business exception.
+     *
+     * @param code  the code
+     * @param cause the cause
+     */
+    public BusinessException(int code, Throwable cause) {
+        super(cause);
+        this.code = code;
+    }
+
+    /**
+     * Gets code.
+     *
+     * @return the code
+     */
+    public int getCode() {
+        return code;
+    }
+
+    /**
+     * Sets code.
+     *
+     * @param code the code
+     */
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    /**
+     * Is unknown boolean.
+     *
+     * @return the boolean
+     */
+    public boolean isUnknown() {
+        return code == UNKNOWN_EXCEPTION;
+    }
+
+
+}

+ 82 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/listener/CalculateExamPaperAnswerListener.java

@@ -0,0 +1,82 @@
+package vip.xiaonuo.exam.listener;
+
+
+import vip.xiaonuo.exam.domain.*;
+import vip.xiaonuo.exam.domain.enums.ExamPaperTypeEnum;
+import vip.xiaonuo.exam.domain.enums.QuestionTypeEnum;
+import vip.xiaonuo.exam.event.CalculateExamPaperAnswerCompleteEvent;
+import vip.xiaonuo.exam.service.ExamPaperAnswerService;
+import vip.xiaonuo.exam.service.ExamPaperQuestionCustomerAnswerService;
+import vip.xiaonuo.exam.service.TaskExamCustomerAnswerService;
+import vip.xiaonuo.exam.service.TextContentService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.List;
+
+
+/**
+ * @version 3.5.0
+ * @description: The type Calculate exam paper answer listener.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@Component
+public class CalculateExamPaperAnswerListener implements ApplicationListener<CalculateExamPaperAnswerCompleteEvent> {
+
+    private final ExamPaperAnswerService examPaperAnswerService;
+    private final ExamPaperQuestionCustomerAnswerService examPaperQuestionCustomerAnswerService;
+    private final TextContentService textContentService;
+    private final TaskExamCustomerAnswerService examCustomerAnswerService;
+
+    /**
+     * Instantiates a new Calculate exam paper answer listener.
+     *
+     * @param examPaperAnswerService                 the exam paper answer service
+     * @param examPaperQuestionCustomerAnswerService the exam paper question customer answer service
+     * @param textContentService                     the text content service
+     * @param examCustomerAnswerService              the exam customer answer service
+     */
+    @Autowired
+    public CalculateExamPaperAnswerListener(ExamPaperAnswerService examPaperAnswerService, ExamPaperQuestionCustomerAnswerService examPaperQuestionCustomerAnswerService, TextContentService textContentService, TaskExamCustomerAnswerService examCustomerAnswerService) {
+        this.examPaperAnswerService = examPaperAnswerService;
+        this.examPaperQuestionCustomerAnswerService = examPaperQuestionCustomerAnswerService;
+        this.textContentService = textContentService;
+        this.examCustomerAnswerService = examCustomerAnswerService;
+    }
+
+    @Override
+    @Transactional
+    public void onApplicationEvent(CalculateExamPaperAnswerCompleteEvent calculateExamPaperAnswerCompleteEvent) {
+        Date now = new Date();
+
+        ExamPaperAnswerInfo examPaperAnswerInfo = (ExamPaperAnswerInfo) calculateExamPaperAnswerCompleteEvent.getSource();
+        ExamPaper examPaper = examPaperAnswerInfo.getExamPaper();
+        ExamPaperAnswer examPaperAnswer = examPaperAnswerInfo.getExamPaperAnswer();
+        List<ExamPaperQuestionCustomerAnswer> examPaperQuestionCustomerAnswers = examPaperAnswerInfo.getExamPaperQuestionCustomerAnswers();
+
+        examPaperAnswerService.insertByFilter(examPaperAnswer);
+        examPaperQuestionCustomerAnswers.stream().filter(a -> QuestionTypeEnum.needSaveTextContent(a.getQuestionType())).forEach(d -> {
+            TextContent textContent = new TextContent(d.getAnswer(), now);
+            textContentService.insertByFilter(textContent);
+            d.setTextContentId(textContent.getId());
+            d.setAnswer(null);
+        });
+        examPaperQuestionCustomerAnswers.forEach(d -> {
+            d.setExamPaperAnswerId(examPaperAnswer.getId());
+        });
+        examPaperQuestionCustomerAnswerService.insertList(examPaperQuestionCustomerAnswers);
+
+        switch (ExamPaperTypeEnum.fromCode(examPaper.getPaperType())) {
+            case Task: {
+                examCustomerAnswerService.insertOrUpdate(examPaper, examPaperAnswer, now);
+                break;
+            }
+            default:
+                break;
+        }
+    }
+}

+ 25 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/listener/EmailSendListener.java

@@ -0,0 +1,25 @@
+package vip.xiaonuo.exam.listener;
+
+import vip.xiaonuo.exam.domain.User;
+import vip.xiaonuo.exam.event.OnRegistrationCompleteEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.lang.NonNull;
+import org.springframework.stereotype.Component;
+
+
+/**
+ * @version 3.5.0
+ * @description: The type Email send listener.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@Component
+public class EmailSendListener implements ApplicationListener<OnRegistrationCompleteEvent> {
+
+    @Override
+    @NonNull
+    public void onApplicationEvent(OnRegistrationCompleteEvent event) {
+        User user = event.getUser();
+        System.out.println("User register Email sender :" + user.getUserName());
+    }
+}

+ 35 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/listener/UserLogListener.java

@@ -0,0 +1,35 @@
+package vip.xiaonuo.exam.listener;
+
+import vip.xiaonuo.exam.event.UserEvent;
+import vip.xiaonuo.exam.service.UserEventLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * @version 3.5.0
+ * @description: The type User log listener.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+@Component
+public class UserLogListener implements ApplicationListener<UserEvent> {
+
+    private final UserEventLogService userEventLogService;
+
+    /**
+     * Instantiates a new User log listener.
+     *
+     * @param userEventLogService the user event log service
+     */
+    @Autowired
+    public UserLogListener(UserEventLogService userEventLogService) {
+        this.userEventLogService = userEventLogService;
+    }
+
+    @Override
+    public void onApplicationEvent(UserEvent userEvent) {
+        userEventLogService.insertByFilter(userEvent.getUserEventLog());
+    }
+
+}

+ 16 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/BaseMapper.java

@@ -0,0 +1,16 @@
+package vip.xiaonuo.exam.repository;
+
+public interface BaseMapper<T> {
+
+    int deleteByPrimaryKey(Integer id);
+
+    int insert(T record);
+
+    int insertSelective(T record);
+
+    T selectByPrimaryKey(Integer id);
+
+    int updateByPrimaryKeySelective(T record);
+
+    int updateByPrimaryKey(T record);
+}

+ 25 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/ExamPaperAnswerMapper.java

@@ -0,0 +1,25 @@
+package vip.xiaonuo.exam.repository;
+
+import vip.xiaonuo.exam.domain.ExamPaperAnswer;
+import vip.xiaonuo.exam.domain.other.KeyValue;
+import vip.xiaonuo.exam.viewmodel.admin.paper.ExamPaperAnswerPageRequestVM;
+import vip.xiaonuo.exam.viewmodel.student.exampaper.ExamPaperAnswerPageVM;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+@Mapper
+public interface ExamPaperAnswerMapper extends BaseMapper<ExamPaperAnswer> {
+
+    List<ExamPaperAnswer> studentPage(ExamPaperAnswerPageVM requestVM);
+
+    Integer selectAllCount();
+
+    List<KeyValue> selectCountByDate(@Param("startTime") Date startTime, @Param("endTime") Date endTime);
+
+    ExamPaperAnswer getByPidUid(@Param("pid") Integer paperId, @Param("uid") Integer uid);
+
+    List<ExamPaperAnswer> adminPage(ExamPaperAnswerPageRequestVM requestVM);
+}

+ 33 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/ExamPaperMapper.java

@@ -0,0 +1,33 @@
+package vip.xiaonuo.exam.repository;
+
+import vip.xiaonuo.exam.domain.ExamPaper;
+import vip.xiaonuo.exam.domain.other.KeyValue;
+import vip.xiaonuo.exam.viewmodel.admin.exam.ExamPaperPageRequestVM;
+import vip.xiaonuo.exam.viewmodel.student.dashboard.PaperFilter;
+import vip.xiaonuo.exam.viewmodel.student.dashboard.PaperInfo;
+import vip.xiaonuo.exam.viewmodel.student.exam.ExamPaperPageVM;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+@Mapper
+public interface ExamPaperMapper extends BaseMapper<ExamPaper> {
+
+    List<ExamPaper> page(ExamPaperPageRequestVM requestVM);
+
+    List<ExamPaper> taskExamPage(ExamPaperPageRequestVM requestVM);
+
+    List<ExamPaper> studentPage(ExamPaperPageVM requestVM);
+
+    List<PaperInfo> indexPaper(PaperFilter paperFilter);
+
+    Integer selectAllCount();
+
+    List<KeyValue> selectCountByDate(@Param("startTime") Date startTime, @Param("endTime") Date endTime);
+
+    int updateTaskPaper(@Param("taskId") Integer taskId,@Param("paperIds") List<Integer> paperIds);
+
+    int clearTaskPaper(@Param("paperIds") List<Integer> paperIds);
+}

+ 27 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/ExamPaperQuestionCustomerAnswerMapper.java

@@ -0,0 +1,27 @@
+package vip.xiaonuo.exam.repository;
+
+import vip.xiaonuo.exam.domain.ExamPaperQuestionCustomerAnswer;
+import vip.xiaonuo.exam.domain.other.ExamPaperAnswerUpdate;
+import vip.xiaonuo.exam.domain.other.KeyValue;
+import vip.xiaonuo.exam.viewmodel.student.question.answer.QuestionPageStudentRequestVM;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+@Mapper
+public interface ExamPaperQuestionCustomerAnswerMapper extends BaseMapper<ExamPaperQuestionCustomerAnswer> {
+
+    List<ExamPaperQuestionCustomerAnswer> selectListByPaperAnswerId(Integer id);
+
+    List<ExamPaperQuestionCustomerAnswer> studentPage(QuestionPageStudentRequestVM requestVM);
+
+    int insertList(List<ExamPaperQuestionCustomerAnswer> list);
+
+    Integer selectAllCount();
+
+    List<KeyValue> selectCountByDate(@Param("startTime") Date startTime, @Param("endTime") Date endTime);
+
+    int updateScore(List<ExamPaperAnswerUpdate> examPaperAnswerUpdates);
+}

+ 17 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/MessageMapper.java

@@ -0,0 +1,17 @@
+package vip.xiaonuo.exam.repository;
+
+import vip.xiaonuo.exam.domain.Message;
+import vip.xiaonuo.exam.viewmodel.admin.message.MessagePageRequestVM;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface MessageMapper extends BaseMapper<Message> {
+
+    List<Message> page(MessagePageRequestVM requestVM);
+
+    List<Message> selectByIds(List<Integer> ids);
+
+    int readAdd(Integer id);
+}

+ 19 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/MessageUserMapper.java

@@ -0,0 +1,19 @@
+package vip.xiaonuo.exam.repository;
+
+import vip.xiaonuo.exam.domain.MessageUser;
+import vip.xiaonuo.exam.viewmodel.student.user.MessageRequestVM;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface MessageUserMapper extends BaseMapper<MessageUser> {
+
+    List<MessageUser> selectByMessageIds(List<Integer> ids);
+
+    int inserts(List<MessageUser> list);
+
+    List<MessageUser> studentPage(MessageRequestVM requestVM);
+
+    Integer unReadCount(Integer userId);
+}

+ 22 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/QuestionMapper.java

@@ -0,0 +1,22 @@
+package vip.xiaonuo.exam.repository;
+
+import vip.xiaonuo.exam.domain.other.KeyValue;
+import vip.xiaonuo.exam.domain.Question;
+import vip.xiaonuo.exam.viewmodel.admin.question.QuestionPageRequestVM;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+@Mapper
+public interface QuestionMapper extends BaseMapper<Question> {
+
+    List<Question> page(QuestionPageRequestVM requestVM);
+
+    List<Question> selectByIds(@Param("ids") List<Integer> ids);
+
+    Integer selectAllCount();
+
+    List<KeyValue> selectCountByDate(@Param("startTime") Date startTime,@Param("endTime") Date endTime);
+}

+ 17 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/SubjectMapper.java

@@ -0,0 +1,17 @@
+package vip.xiaonuo.exam.repository;
+
+import vip.xiaonuo.exam.domain.Subject;
+import vip.xiaonuo.exam.viewmodel.admin.education.SubjectPageRequestVM;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface SubjectMapper  extends BaseMapper<Subject> {
+
+    List<Subject> getSubjectByLevel(Integer level);
+
+    List<Subject> allSubject();
+
+    List<Subject> page(SubjectPageRequestVM requestVM);
+}

+ 15 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/TaskExamCustomerAnswerMapper.java

@@ -0,0 +1,15 @@
+package vip.xiaonuo.exam.repository;
+
+import vip.xiaonuo.exam.domain.TaskExamCustomerAnswer;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface TaskExamCustomerAnswerMapper extends BaseMapper<TaskExamCustomerAnswer> {
+
+    TaskExamCustomerAnswer getByTUid(@Param("tid") Integer tid, @Param("uid") Integer uid);
+
+    List<TaskExamCustomerAnswer> selectByTUid(@Param("taskIds") List<Integer> taskIds, @Param("uid") Integer uid);
+}

+ 15 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/TaskExamMapper.java

@@ -0,0 +1,15 @@
+package vip.xiaonuo.exam.repository;
+
+import vip.xiaonuo.exam.domain.TaskExam;
+import vip.xiaonuo.exam.viewmodel.admin.task.TaskPageRequestVM;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface TaskExamMapper extends BaseMapper<TaskExam> {
+
+    List<TaskExam> page(TaskPageRequestVM requestVM);
+
+    List<TaskExam> getByGradeLevel(Integer gradeLevel);
+}

+ 9 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/TextContentMapper.java

@@ -0,0 +1,9 @@
+package vip.xiaonuo.exam.repository;
+
+import vip.xiaonuo.exam.domain.TextContent;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface TextContentMapper extends BaseMapper<TextContent> {
+
+}

+ 20 - 0
snowy-plugin/snowy-plugin-exam/snowy-plugin-exam-func/src/main/java/vip/xiaonuo/exam/repository/UserEventLogMapper.java

@@ -0,0 +1,20 @@
+package vip.xiaonuo.exam.repository;
+
+import vip.xiaonuo.exam.domain.UserEventLog;
+import vip.xiaonuo.exam.domain.other.KeyValue;
+import vip.xiaonuo.exam.viewmodel.admin.user.UserEventPageRequestVM;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+@Mapper
+public interface UserEventLogMapper extends BaseMapper<UserEventLog> {
+
+    List<UserEventLog> getUserEventLogByUserId(Integer id);
+
+    List<UserEventLog> page(UserEventPageRequestVM requestVM);
+
+    List<KeyValue> selectCountByDate(@Param("startTime") Date startTime, @Param("endTime") Date endTime);
+}

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor