shilin 2 år sedan
incheckning
30db06f02d
100 ändrade filer med 12033 tillägg och 0 borttagningar
  1. BIN
      docs/开发文档/三方系统同步登录接口.pdf
  2. BIN
      docs/开发文档/源码说明.pdf
  3. BIN
      docs/用户手册/【用户手册】云帆考试系统(学员端)V4.1.pdf
  4. BIN
      docs/用户手册/【用户手册】云帆考试系统(管理端)V4.1.pdf
  5. BIN
      docs/部署资源/1、服务器部署文档.pdf
  6. BIN
      docs/部署资源/2、文档装换组件安装.pdf
  7. BIN
      docs/部署资源/3、基于Docker的部署方法.pdf
  8. 2 0
      docs/部署资源/WinService/ReadMe.txt
  9. BIN
      docs/部署资源/WinService/yf-exam.exe
  10. 8 0
      docs/部署资源/WinService/yf-exam.xml
  11. 5628 0
      docs/部署资源/db/yf_exam_22061703.sql
  12. 112 0
      docs/部署资源/run/application-local.yml
  13. BIN
      docs/部署资源/run/dist.zip
  14. BIN
      docs/部署资源/run/h5.zip
  15. 2 0
      docs/部署资源/run/readme.txt
  16. 1 0
      docs/部署资源/run/start.bat
  17. 1 0
      docs/部署资源/run/start.sh
  18. BIN
      docs/部署资源/run/yf-exam-api.jar
  19. BIN
      docs/配置文档/申请阿里云SSL证书文档.pdf
  20. BIN
      docs/配置文档/阿里云OSS存储配置.pdf
  21. 9 0
      exam-06173-api/.gitignore
  22. 551 0
      exam-06173-api/pom.xml
  23. 42 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/annon/DataProtect.java
  24. 21 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/annon/Dict.java
  25. 30 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/annon/LogInject.java
  26. 67 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/ApiError.java
  27. 63 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/ApiRest.java
  28. 154 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/controller/BaseController.java
  29. 26 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseBatchReqDTO.java
  30. 15 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseDTO.java
  31. 27 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseIdReqDTO.java
  32. 25 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseIdRespDTO.java
  33. 25 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseIdsReqDTO.java
  34. 31 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseIfReqDTO.java
  35. 24 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseListDTO.java
  36. 34 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseQueryReqDTO.java
  37. 31 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseStateReqDTO.java
  38. 23 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseTokenReqDTO.java
  39. 29 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseUserReqDTO.java
  40. 25 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BooleanRespDTO.java
  41. 51 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/PagingReqDTO.java
  42. 30 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/PagingRespDTO.java
  43. 21 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/PayReqDTO.java
  44. 19 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/enums/CommonState.java
  45. 118 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/utils/FileUtils.java
  46. 48 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/utils/JsonConverter.java
  47. 40 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/utils/MsgUtils.java
  48. 53 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/api/utils/SignUtils.java
  49. 59 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/exception/ServiceException.java
  50. 84 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/exception/ServiceExceptionHandler.java
  51. 59 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/BeanMapper.java
  52. 122 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/DateUtils.java
  53. 267 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/DecimalUtils.java
  54. 65 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/IpUtils.java
  55. 69 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/Md5Util.java
  56. 324 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/Reflections.java
  57. 32 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/SpringUtils.java
  58. 100 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/download/DownloadUtil.java
  59. 307 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/download/Downloader.java
  60. 48 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/download/temp/DownloadTemp.java
  61. 58 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/download/temp/DownloadTempThread.java
  62. 173 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/download/thread/DownloadThread.java
  63. 234 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/excel/ExportExcel.java
  64. 258 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/excel/ImportExcel.java
  65. 32 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/excel/MyExcelWriter.java
  66. 55 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/excel/annotation/ExcelField.java
  67. 68 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/file/MD5Util.java
  68. 169 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/file/TextFileUtils.java
  69. 118 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/file/ZipUtils.java
  70. 154 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/http/HttpClientUtil.java
  71. 56 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/passwd/PassHandler.java
  72. 38 0
      exam-06173-api/src/main/java/com/yf/boot/base/api/utils/passwd/PassInfo.java
  73. 217 0
      exam-06173-api/src/main/java/com/yf/boot/base/aspect/data/BaseDataAspect.java
  74. 25 0
      exam-06173-api/src/main/java/com/yf/boot/base/aspect/data/BaseDataService.java
  75. 105 0
      exam-06173-api/src/main/java/com/yf/boot/base/aspect/log/BaseLogAspect.java
  76. 29 0
      exam-06173-api/src/main/java/com/yf/boot/base/aspect/log/BaseLogService.java
  77. 56 0
      exam-06173-api/src/main/java/com/yf/boot/base/aspect/log/enums/LogType.java
  78. 25 0
      exam-06173-api/src/main/java/com/yf/boot/base/enums/DataScope.java
  79. 17 0
      exam-06173-api/src/main/java/com/yf/boot/base/enums/PlatformType.java
  80. 209 0
      exam-06173-api/src/main/java/com/yf/boot/base/kits/db/TableUtils.java
  81. 52 0
      exam-06173-api/src/main/java/com/yf/boot/base/kits/db/entity/DbColumn.java
  82. 35 0
      exam-06173-api/src/main/java/com/yf/boot/base/kits/db/entity/DbTable.java
  83. 30 0
      exam-06173-api/src/main/java/com/yf/boot/base/utils/AbcTags.java
  84. 31 0
      exam-06173-api/src/main/java/com/yf/boot/base/utils/CronUtils.java
  85. 53 0
      exam-06173-api/src/main/java/com/yf/boot/base/utils/FileUtils.java
  86. 57 0
      exam-06173-api/src/main/java/com/yf/boot/base/utils/ImageUtils.java
  87. 127 0
      exam-06173-api/src/main/java/com/yf/boot/base/utils/InjectUtils.java
  88. 91 0
      exam-06173-api/src/main/java/com/yf/boot/base/utils/ListUtils.java
  89. 44 0
      exam-06173-api/src/main/java/com/yf/boot/base/utils/MessageUtils.java
  90. 96 0
      exam-06173-api/src/main/java/com/yf/boot/base/utils/ResourceUtil.java
  91. 98 0
      exam-06173-api/src/main/java/com/yf/exam/ExamApplication.java
  92. 30 0
      exam-06173-api/src/main/java/com/yf/exam/ability/Constant.java
  93. 72 0
      exam-06173-api/src/main/java/com/yf/exam/ability/captcha/controller/CaptchaController.java
  94. 29 0
      exam-06173-api/src/main/java/com/yf/exam/ability/captcha/dto/request/CheckCaptchaReqDTO.java
  95. 24 0
      exam-06173-api/src/main/java/com/yf/exam/ability/captcha/service/CaptchaService.java
  96. 58 0
      exam-06173-api/src/main/java/com/yf/exam/ability/captcha/service/impl/CaptchaServiceImpl.java
  97. 22 0
      exam-06173-api/src/main/java/com/yf/exam/ability/desensitize/annon/Desensitized.java
  98. 10 0
      exam-06173-api/src/main/java/com/yf/exam/ability/desensitize/enums/DesensitizeType.java
  99. 56 0
      exam-06173-api/src/main/java/com/yf/exam/ability/desensitize/filter/DesensitizeFilter.java
  100. 0 0
      exam-06173-api/src/main/java/com/yf/exam/ability/desensitize/utils/DesensitizeUtils.java

BIN
docs/开发文档/三方系统同步登录接口.pdf


BIN
docs/开发文档/源码说明.pdf


BIN
docs/用户手册/【用户手册】云帆考试系统(学员端)V4.1.pdf


BIN
docs/用户手册/【用户手册】云帆考试系统(管理端)V4.1.pdf


BIN
docs/部署资源/1、服务器部署文档.pdf


BIN
docs/部署资源/2、文档装换组件安装.pdf


BIN
docs/部署资源/3、基于Docker的部署方法.pdf


+ 2 - 0
docs/部署资源/WinService/ReadMe.txt

@@ -0,0 +1,2 @@
+1、把yf-exam-api.jar和application-local.yml放到本目录
+2、运行命令注册为Windows系统服务:yf-exam.exe install

BIN
docs/部署资源/WinService/yf-exam.exe


+ 8 - 0
docs/部署资源/WinService/yf-exam.xml

@@ -0,0 +1,8 @@
+ <service>
+  <id>yf-exam</id>
+  <name>CloudExam</name>
+  <description>云帆在线考试系统服务,运行在http://localhost:8101</description>
+  <executable>java</executable>
+  <arguments>-jar yf-exam-api.jar --spring.config.location=application-local.yml</arguments>
+  <log mode="roll"></log>
+</service>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 5628 - 0
docs/部署资源/db/yf_exam_22061703.sql


+ 112 - 0
docs/部署资源/run/application-local.yml

@@ -0,0 +1,112 @@
+# 完整的外挂配置文件,修改配置无需重新打包
+# 运行命令:java -jar yf-exam-api.jar --spring.config.location=application-local.yml
+server:
+  port: 8101
+  # 启用服务端压缩
+  compression:
+    enabled: true
+    min-response-size: 10
+    mime-types: application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css
+
+spring:
+  application:
+    name: yf-exam-api
+  profiles:
+    active: dev
+  main:
+    allow-bean-definition-overriding: true
+  # 数据库配置
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://127.0.0.1:3306/yf_exam?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+    username: root
+    password: root
+    filters: stat
+    max-active: 1000
+    initial-size: 10
+    max-wait: 6000
+    min-idle: 1
+    time-between-eviction-runs-millis: 60000
+    min-evictable-idle-time-millis: 300000
+    test-while-idle: true
+    test-on-borrow: false
+    test-on-return: false
+    pool-prepared-statements: true
+    max-open-prepared-statements: 1000
+    async-init: true
+
+  # Redis配置
+  redis:
+    database: 0
+    host: 127.0.0.1
+    port: 6379
+    password:
+    timeout: 5000
+
+  # 定时任务配置
+  quartz:
+    #数据库方式
+    job-store-type: jdbc
+    # quartz 相关属性配置
+    properties:
+      org:
+        quartz:
+          scheduler:
+            instanceName: examScheduler
+            instanceId: AUTO
+          jobStore:
+            class: org.quartz.impl.jdbcjobstore.JobStoreTX
+            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+            tablePrefix: QRTZ_
+            isClustered: true
+            clusterCheckinInterval: 10000
+            useProperties: false
+          threadPool:
+            class: org.quartz.simpl.SimpleThreadPool
+            threadCount: 10
+            threadPriority: 5
+            threadsInheritContextClassLoaderOfInitializingThread: true
+
+# 文档转换,请先安装LibreOffice,再配置相应路径,enabled=true
+jodconverter:
+  local:
+    enabled: false
+    office-home: /opt/libreoffice6.4
+    max-tasks-per-process: 10
+    port-numbers: 8100
+
+
+# 基础配置
+ycloud:
+  # 运行模式,ture为演示/false为正式
+  demo: false
+  # false运行多处登录,true为T下线
+  login-tick: false
+  # 微信登录成功以后,将token推送到这个链接同步登录
+  login-sync-pc: https://youdomain.com/#/sync/{token}/{roleType}
+  # 微信登录成功以后,将token推送到这个链接同步登录
+  login-sync-h5: https://youdomain.com/#/pages/login/sync?token={token}
+  # 微信获取code以后回调此URL
+  login-redirect: https://youdomain.com/api/common/wx/redirect
+
+# 微信相关配置
+wechat:
+  login:
+    # 小程序登录appId
+    mp-app-id:
+    mp-app-secret:
+    # 网站登录appId
+    site-app-id:
+    site-app-secret:
+
+# 生产环境建议关闭文档
+# 文档访问地址:http://localhost:8101/doc.html
+swagger:
+  enable: false
+
+logging:
+  level:
+    root: error
+  # 日志文件目录
+  path: logs/${spring.application.name}/

BIN
docs/部署资源/run/dist.zip


BIN
docs/部署资源/run/h5.zip


+ 2 - 0
docs/部署资源/run/readme.txt

@@ -0,0 +1,2 @@
+此目录为外挂配置运行方式,Windows运行start.bat,Linux运行start.sh
+最新的运行文件重命名为:yf-exam-api.jar 放在此目录下即可!

+ 1 - 0
docs/部署资源/run/start.bat

@@ -0,0 +1 @@
+java -jar yf-exam-api.jar --spring.config.location=application-local.yml

+ 1 - 0
docs/部署资源/run/start.sh

@@ -0,0 +1 @@
+java -jar yf-exam-api.jar --spring.config.location=application-local.yml

BIN
docs/部署资源/run/yf-exam-api.jar


BIN
docs/配置文档/申请阿里云SSL证书文档.pdf


BIN
docs/配置文档/阿里云OSS存储配置.pdf


+ 9 - 0
exam-06173-api/.gitignore

@@ -0,0 +1,9 @@
+/docs/docker/runtime/fonts.tar.gz
+/docs/docker/runtime/jdk-8u281-linux-x64.tar.gz
+/docs/docker/runtime/LibreOffice_6.4.7_Linux_x86-64_rpm.tar.gz
+/docs/docker/runtime/LibreOffice_6.4.7_Linux_x86-64_rpm_langpack_zh-CN.tar.gz
+/target/
+/docs/部署资源/docker/data/
+/docs/部署资源/docker/mysql-service/db/
+/docs/部署资源/docker/redis-service/db/
+/docs/部署资源/docker/exam-service/soft/

+ 551 - 0
exam-06173-api/pom.xml

@@ -0,0 +1,551 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.yfhl</groupId>
+    <artifactId>yf-exam-api</artifactId>
+    <packaging>jar</packaging>
+    <version>1.0</version>
+    <name>yf-exam-api</name>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.1.4.RELEASE</version>
+    </parent>
+
+    <properties>
+        <fastjson.version>1.2.83</fastjson.version>
+        <oss.version>3.7.0</oss.version>
+        <aliyun.sdk.version>4.4.9</aliyun.sdk.version>
+        <swagger.version>2.9.2</swagger.version>
+        <dozer.version>5.5.1</dozer.version>
+        <apache.commons.version>3.8</apache.commons.version>
+        <mysql.driver.version>8.0.11</mysql.driver.version>
+        <mybatis-plus.version>3.3.2</mybatis-plus.version>
+        <lombok.version>1.18.4</lombok.version>
+        <thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
+        <alicloud.version>2.1.1.RELEASE</alicloud.version>
+        <poi.version>4.1.2</poi.version>
+        <log4j2.version>2.16.0</log4j2.version>
+        <shiro.version>1.8.0</shiro.version>
+    </properties>
+
+
+    <!-- 设定仓库 -->
+
+    <repositories>
+
+        <repository>
+            <id>aliyun-repos</id>
+            <url>https://maven.aliyun.com/repository/public</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+
+        <repository>
+            <id>sonatype-repos-s</id>
+            <name>Sonatype Repository</name>
+            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+            <releases>
+                <enabled>false</enabled>
+            </releases>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+
+    </repositories>
+
+
+    <pluginRepositories>
+        <pluginRepository>
+            <id>aliyun-repos</id>
+            <url>https://maven.aliyun.com/repository/public</url>
+        </pluginRepository>
+    </pluginRepositories>
+
+
+    <dependencies>
+
+
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjweaver</artifactId>
+            <version>1.9.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>${fastjson.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>net.sf.dozer</groupId>
+            <artifactId>dozer</artifactId>
+            <version>${dozer.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-collections</groupId>
+                    <artifactId>commons-collections</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus</artifactId>
+            <version>${mybatis-plus.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${mybatis-plus.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>${mysql.driver.version}</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.dom4j</groupId>
+            <artifactId>dom4j</artifactId>
+            <version>2.1.1</version>
+        </dependency>
+
+
+
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>${swagger.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-ui</artifactId>
+            <version>2.0.9</version>
+        </dependency>
+
+        <!-- poi office -->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>${poi.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>${poi.version}</version>
+        </dependency>
+
+
+        <!--JWT-->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>3.7.0</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.4.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <version>2.9.8</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>2.9.8</version>
+        </dependency>
+
+        <!-- WEB支持 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+
+        <!-- 缓存支持 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+
+        <!-- SpringBoot Redis支持 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <!--spring quartz依赖-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-quartz</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>${lombok.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+
+        <!--shiro-->
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-spring-boot-starter</artifactId>
+            <version>${shiro.version}</version>
+        </dependency>
+        <!-- shiro-redis -->
+        <dependency>
+            <groupId>org.crazycake</groupId>
+            <artifactId>shiro-redis</artifactId>
+            <version>3.1.0</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.shiro</groupId>
+                    <artifactId>shiro-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>guava</artifactId>
+                    <groupId>com.google.guava</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- 文件转换,通过OpenOffice或LibreOffice来转换 -->
+        <dependency>
+            <groupId>org.jodconverter</groupId>
+            <artifactId>jodconverter-core</artifactId>
+            <version>4.4.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jodconverter</groupId>
+            <artifactId>jodconverter-spring-boot-starter</artifactId>
+            <version>4.4.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jodconverter</groupId>
+            <artifactId>jodconverter-local</artifactId>
+            <version>4.4.2</version>
+        </dependency>
+
+
+        <!-- WebSocket用于文件阅读 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>4.4.9</version>
+        </dependency>
+
+
+        <!-- OSS文件上传及文档转换 -->
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>${oss.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-imm</artifactId>
+            <version>1.22.0</version>
+        </dependency>
+
+        <!-- 阿里云视频转码 -->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-mts</artifactId>
+            <version>2.5.2</version>
+        </dependency>
+
+
+        <!-- 直播SDK -->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-live</artifactId>
+            <version>3.9.0</version>
+        </dependency>
+
+        <!-- 腾讯云 -->
+        <dependency>
+            <groupId>com.qcloud</groupId>
+            <artifactId>cos_api</artifactId>
+            <version>5.6.52</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.qcloud</groupId>
+            <artifactId>cos-sts_api</artifactId>
+            <version>3.1.0</version>
+        </dependency>
+
+
+        <!-- 七牛云 -->
+        <dependency>
+            <groupId>com.qiniu</groupId>
+            <artifactId>qiniu-java-sdk</artifactId>
+            <version>[7.4.0, 7.4.99]</version>
+        </dependency>
+
+
+        <!-- 图形验证码 -->
+        <dependency>
+            <groupId>com.github.whvcse</groupId>
+            <artifactId>easy-captcha</artifactId>
+            <version>1.6.2</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dysmsapi20170525</artifactId>
+            <version>2.0.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>1.2.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.11.0</version>
+        </dependency>
+
+
+        <!-- 阿里云人脸认证 start -->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>tea-openapi</artifactId>
+            <version>0.0.19</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>facebody20191230</artifactId>
+            <version>1.0.19</version>
+        </dependency>
+
+
+        <!-- 用于上传临时文件的 -->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>viapi-utils</artifactId>
+            <version>1.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-viapiutils</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <!-- 阿里云人脸认证 end -->
+
+
+        <!-- 百度人脸认证 start -->
+        <dependency>
+            <groupId>com.baidu.aip</groupId>
+            <artifactId>java-sdk</artifactId>
+            <version>4.16.2</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>slf4j-simple</artifactId>
+                    <groupId>org.slf4j</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- 百度人脸认证 end -->
+
+
+        <!-- 微信支付 start -->
+        <dependency>
+            <groupId>com.github.wechatpay-apiv3</groupId>
+            <artifactId>wechatpay-apache-httpclient</artifactId>
+            <version>0.2.2</version>
+        </dependency>
+        <!-- 微信支付 end -->
+
+        <!-- 支付宝支付 start -->
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+            <version>4.16.11.ALL</version>
+        </dependency>
+        <!-- 支付宝支付 end -->
+
+
+        <!-- 腾讯云人脸识别 -->
+        <dependency>
+            <groupId>com.tencentcloudapi</groupId>
+            <artifactId>tencentcloud-sdk-java-iai</artifactId>
+            <version>3.1.471</version>
+        </dependency>
+
+
+        <!-- 通用工具包 -->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-poi</artifactId>
+            <version>5.7.17</version>
+        </dependency>
+
+        <!-- 证书模板 -->
+        <dependency>
+            <groupId>com.deepoove</groupId>
+            <artifactId>poi-tl</artifactId>
+            <version>1.10.0</version>
+        </dependency>
+
+        <!-- 文件工具获取MineType -->
+        <dependency>
+            <groupId>org.apache.tika</groupId>
+            <artifactId>tika-core</artifactId>
+            <version>2.4.0</version>
+        </dependency>
+
+        <!-- 获取视频时长 -->
+        <dependency>
+            <groupId>com.googlecode.mp4parser</groupId>
+            <artifactId>isoparser</artifactId>
+            <version>1.1.22</version>
+        </dependency>
+
+
+    </dependencies>
+
+
+    <build>
+        <finalName>${project.name}</finalName>
+        <defaultGoal>compile</defaultGoal>
+        <plugins>
+
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <version>3.1.2</version>
+                <executions>
+                    <execution>
+                        <id>copy-dependencies</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <!-- 拷贝项目依赖包到libs/目录下 -->
+                            <outputDirectory>${project.build.directory}/libs</outputDirectory>
+                            <!-- 间接依赖也拷贝 -->
+                            <excludeTransitive>false</excludeTransitive>
+                            <!-- 带上版本号 -->
+                            <stripVersion>false</stripVersion>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.1.4.RELEASE</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+
+                <configuration>
+                    <layout>ZIP</layout>
+                    <includeSystemScope>true</includeSystemScope>
+                    <!-- 调试或者完整打包注释以下内容 -->
+                    <!--                    <includes>-->
+                    <!--                        <include>-->
+                    <!--                            <groupId>non-exists</groupId>-->
+                    <!--                            <artifactId>non-exists</artifactId>-->
+                    <!--                        </include>-->
+                    <!--                    </includes>-->
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.7.0</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.22.1</version>
+                <configuration>
+                    <skipTests>true</skipTests>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>dockerfile-maven-plugin</artifactId>
+                <version>1.4.12</version>
+                <configuration>
+                    <!-- 阿里镜像仓库 -->
+                    <repository>${project.build.finalName}</repository>
+                    <buildArgs>
+                        <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
+                    </buildArgs>
+                </configuration>
+            </plugin>
+
+        </plugins>
+    </build>
+</project>

+ 42 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/annon/DataProtect.java

@@ -0,0 +1,42 @@
+package com.yf.boot.base.api.annon;
+
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 保护某些数据不被删除或更新
+ * @author bool
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface DataProtect {
+
+    /**
+     * 更新保护
+     * @return
+     */
+    boolean update() default false;
+
+    /**
+     * 删除保护
+     * @return
+     */
+    boolean delete() default false;
+
+    /**
+     * 当前用户ID
+     * @return
+     */
+    boolean currUsr() default false;
+
+    /**
+     * 实体类名,获取表名
+     * @return
+     */
+    Class clazz();
+}

+ 21 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/annon/Dict.java

@@ -0,0 +1,21 @@
+package com.yf.boot.base.api.annon;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 数据字典注解
+ * @author bool
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Dict {
+
+    String dicCode();
+
+    String dicText() default "";
+
+    String dictTable() default "";
+}

+ 30 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/annon/LogInject.java

@@ -0,0 +1,30 @@
+package com.yf.boot.base.api.annon;
+
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 系统日志注入类
+ * @author bool
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface LogInject {
+
+    /**
+     * 日志类型
+     * @return
+     */
+    String logType() default "日志类型";
+
+    /**
+     * 日志标题
+     * @return
+     */
+    String title() default "系统日志";
+}

+ 67 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/ApiError.java

@@ -0,0 +1,67 @@
+package com.yf.boot.base.api.api;
+
+
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 全局错误码定义,用于定义接口的响应数据,
+ * 枚举名称全部使用代码命名,在系统中调用,免去取名难的问题。
+ * @author bool 
+ * @date 2019-06-14 21:15
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+public enum ApiError implements Serializable {
+
+
+    /**
+     * 通用错误,接口参数不全
+     */
+    ERROR_10010001("参数不全或类型错误!"),
+    ERROR_10010002("您还未登录,请先登录!"),
+    ERROR_10010003("数据不存在!"),
+    ERROR_10010012("图形验证码错误!"),
+    ERROR_10010013("短信验证码错误!"),
+    ERROR_10010014("不允许重复评论!"),
+
+    /**
+     * 考试相关错误
+     */
+    ERROR_20010001("试题被删除,无法继续考试!"),
+    ERROR_20010002("您有正在进行的考试!"),
+
+
+    ERROR_90010001("账号不存在,请确认!"),
+    ERROR_90010002("账号或密码错误!"),
+    ERROR_90010003("至少要包含一个角色!"),
+    ERROR_90010004("管理员账号无法修改!"),
+    ERROR_90010005("账号被禁用,请联系管理员!"),
+    ERROR_90010006("活动用户不足,无法开启竞拍!"),
+    ERROR_90010007("旧密码不正确,请确认!"),
+
+
+    ERROR_60000001("数据不存在!");
+
+    public String msg;
+
+    /**
+     * 生成Markdown格式文档,用于更新文档用的
+     * @param args
+     */
+    public static void main(String[] args) {
+        for (ApiError e : ApiError.values()) {
+            System.out.println("'"+e.name().replace("ERROR_", "")+"':'"+e.msg+"',");
+        }
+    }
+
+    /**
+     * 获取错误码
+     * @return
+     */
+    public Integer getCode(){
+        return Integer.parseInt(this.name().replace("ERROR_", ""));
+    }
+}

+ 63 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/ApiRest.java

@@ -0,0 +1,63 @@
+package com.yf.boot.base.api.api;
+
+
+import com.yf.boot.base.api.exception.ServiceException;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 数据结果返回的封装
+ * @author bool 
+ * @date 2018/11/20 09:48
+ */
+@Data
+@NoArgsConstructor
+@ApiModel(value="接口响应", description="接口响应")
+public class ApiRest<T>{
+
+    /**
+     * 响应消息
+     */
+    @ApiModelProperty(value = "响应消息")
+    private String msg;
+    /**
+     * 响应代码
+     */
+    @ApiModelProperty(value = "响应代码,0为成功,1为失败", required = true)
+    private Integer code;
+
+    /**
+     * 请求或响应body
+     */
+    @ApiModelProperty(value = "响应内容")
+    protected T data;
+
+
+    /**
+     * 是否成功
+     * @return
+     */
+    public boolean isSuccess(){
+        return code.equals(0);
+    }
+
+    /**
+     * 构造函数
+     * @param error
+     */
+    public ApiRest(ServiceException error){
+        this.code = error.getCode();
+        this.msg = error.getMsg();
+    }
+
+    /**
+     * 构造函数
+     * @param error
+     */
+    public ApiRest(ApiError error){
+        this.code = error.getCode();
+        this.msg = error.msg;
+    }
+}

+ 154 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/controller/BaseController.java

@@ -0,0 +1,154 @@
+package com.yf.boot.base.api.api.controller;
+
+
+import com.yf.boot.base.api.api.ApiError;
+import com.yf.boot.base.api.api.ApiRest;
+import com.yf.boot.base.api.exception.ServiceException;
+
+/**
+ * 基础控制器
+ * @author Dav
+ */
+public class BaseController {
+
+    /**
+     * 成功默认消息
+     */
+    private static final Integer CODE_SUCCESS = 0;
+    private static final String MSG_SUCCESS = "操作成功!";
+
+    /**
+     * 失败默认消息
+     */
+    private static final Integer CODE_FAILURE = 1;
+    private static final String MSG_FAILURE = "请求失败!";
+
+
+    /**
+     * 完成消息构造
+     * @param code
+     * @param message
+     * @param data
+     * @param <T>
+     * @return
+     */
+    protected <T> ApiRest<T> message(Integer code, String message, T data){
+        ApiRest<T> response = new ApiRest<>();
+        response.setCode(code);
+        response.setMsg(message);
+        if(data!=null) {
+            response.setData(data);
+        }
+        return response;
+    }
+
+    /**
+     * 请求成功空数据
+     * @param <T>
+     * @return
+     */
+    protected <T> ApiRest<T> success(){
+        return message(0, "请求成功!", null);
+    }
+
+
+
+    /**
+     * 请求成功,通用代码
+     * @param message
+     * @param data
+     * @param <T>
+     * @return
+     */
+    protected <T> ApiRest<T> success(String message, T data){
+        return message(CODE_SUCCESS, message, data);
+    }
+
+
+    /**
+     * 请求成功,仅内容
+     * @param data
+     * @param <T>
+     * @return
+     */
+    protected <T> ApiRest<T> success(T data){
+        return message(CODE_SUCCESS, MSG_SUCCESS, data);
+    }
+
+
+    /**
+     * 请求失败,完整构造
+     * @param code
+     * @param message
+     * @param data
+     * @param <T>
+     * @return
+     */
+    protected <T> ApiRest<T> failure(Integer code, String message, T data){
+        return message(code, message, data);
+    }
+
+    /**
+     * 请求失败,消息和内容
+     * @param message
+     * @param data
+     * @param <T>
+     * @return
+     */
+    protected <T> ApiRest<T> failure(String message, T data){
+        return message(CODE_FAILURE, message, data);
+    }
+
+    /**
+     * 请求失败,消息
+     * @param message
+     * @return
+     */
+    protected <T> ApiRest<T> failure(String message){
+        return message(CODE_FAILURE, message, null);
+    }
+
+    /**
+     * 请求失败,仅内容
+     * @param data
+     * @param <T>
+     * @return
+     */
+    protected <T> ApiRest<T> failure(T data){
+        return message(CODE_FAILURE, MSG_FAILURE, data);
+    }
+
+
+    /**
+     * 请求失败,仅内容
+     * @param <T>
+     * @return
+     */
+    protected <T> ApiRest<T> failure(){
+        return message(CODE_FAILURE, MSG_FAILURE, null);
+    }
+
+
+
+    /**
+     * 请求失败,仅内容
+     * @param <T>
+     * @return
+     */
+    protected <T> ApiRest<T> failure(ApiError error, T data){
+        return message(error.getCode(), error.msg, data);
+    }
+
+
+
+    /**
+     * 请求失败,仅内容
+     * @param ex
+     * @param <T>
+     * @return
+     */
+    protected <T> ApiRest<T> failure(ServiceException ex){
+        ApiRest<T> apiRest = message(ex.getCode(), ex.getMsg(), null);
+        return apiRest;
+    }
+}

+ 26 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseBatchReqDTO.java

@@ -0,0 +1,26 @@
+package com.yf.boot.base.api.api.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 通用批量操作请求类
+ * </p>
+ *
+ * @author 聪明笨狗
+ * @since 2019-04-20 12:15
+ */
+@Data
+@ApiModel(value="通用批量操作请求类", description="通用批量操作请求类")
+public class BaseBatchReqDTO extends BaseDTO {
+
+    @ApiModelProperty(value = "要修改的对象列表", required=true)
+    private List<String> ids;
+
+    @ApiModelProperty(value = "修改成为的值", required=true)
+    private String toId;
+}

+ 15 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseDTO.java

@@ -0,0 +1,15 @@
+package com.yf.boot.base.api.api.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 请求和响应的基础类,用于处理序列化
+ * @author dav
+ * @date 2019/3/16 15:56
+ */
+@Data
+public class BaseDTO implements Serializable {
+
+}

+ 27 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseIdReqDTO.java

@@ -0,0 +1,27 @@
+package com.yf.boot.base.api.api.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * <p>
+ * 主键通用请求类,用于根据ID查询
+ * </p>
+ *
+ * @author 聪明笨狗
+ * @since 2019-04-20 12:15
+ */
+@Data
+@ApiModel(value="主键通用请求类", description="主键通用请求类")
+public class BaseIdReqDTO extends BaseDTO {
+
+
+    @ApiModelProperty(value = "主键ID", required=true)
+    private String id;
+
+    @JsonIgnore
+    private String userId;
+
+}

+ 25 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseIdRespDTO.java

@@ -0,0 +1,25 @@
+package com.yf.boot.base.api.api.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * <p>
+ * 主键通用响应类,用于添加后返回内容
+ * </p>
+ *
+ * @author 聪明笨狗
+ * @since 2019-04-20 12:15
+ */
+@Data
+@ApiModel(value="主键通用响应类", description="主键通用响应类")
+@AllArgsConstructor
+@NoArgsConstructor
+public class BaseIdRespDTO extends BaseDTO {
+
+    @ApiModelProperty(value = "主键ID", required=true)
+    private String id;
+}

+ 25 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseIdsReqDTO.java

@@ -0,0 +1,25 @@
+package com.yf.boot.base.api.api.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 通用ID列表类操作,用于批量删除、修改状态等
+ * @author bool 
+ * @date 2019-08-01 19:07
+ */
+@Data
+@ApiModel(value="删除参数", description="删除参数")
+public class BaseIdsReqDTO extends BaseDTO {
+
+
+    @JsonIgnore
+    private String userId;
+
+    @ApiModelProperty(value = "要删除的ID列表", required = true)
+    private List<String> ids;
+}

+ 31 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseIfReqDTO.java

@@ -0,0 +1,31 @@
+package com.yf.boot.base.api.api.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 是否状态通用请求
+ * </p>
+ *
+ * @author 聪明笨狗
+ * @since 2019-04-20 12:15
+ */
+@Data
+@ApiModel(value="是否状态通用请求", description="是否状态通用请求")
+@AllArgsConstructor
+@NoArgsConstructor
+public class BaseIfReqDTO extends BaseDTO {
+
+
+    @ApiModelProperty(value = "要修改对象的ID列表", required=true)
+    private List<String> ids;
+
+    @ApiModelProperty(value = "启用状态,true/false", required=true)
+    private Boolean enabled;
+}

+ 24 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseListDTO.java

@@ -0,0 +1,24 @@
+package com.yf.boot.base.api.api.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 请求和响应的基础类,用于处理序列化
+ * @author dav
+ * @date 2019/3/16 15:56
+ */
+@Data
+public class BaseListDTO<T> implements Serializable {
+
+    @JsonIgnore
+    private String userId;
+
+    /**
+     * 数据列表
+     */
+    private List<T> items;
+}

+ 34 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseQueryReqDTO.java

@@ -0,0 +1,34 @@
+package com.yf.boot.base.api.api.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+/**
+ * <p>
+ * 按关键字查询请求通用类
+ * </p>
+ *
+ * @author 聪明笨狗
+ * @since 2019-04-20 12:15
+ */
+@Data
+@ApiModel(value="按关键字查询请求通用类", description="按关键字查询请求通用类")
+@AllArgsConstructor
+@NoArgsConstructor
+public class BaseQueryReqDTO extends BaseDTO {
+
+
+    @ApiModelProperty(value = "日期开始", required=true)
+    private Date statDateL;
+
+    @ApiModelProperty(value = "日期结束", required=true)
+    private Date statDateR;
+
+    @ApiModelProperty(value = "关键字查询", required=true)
+    private String q;
+}

+ 31 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseStateReqDTO.java

@@ -0,0 +1,31 @@
+package com.yf.boot.base.api.api.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 通用状态请求类,用于修改状态什么的
+ * </p>
+ *
+ * @author 聪明笨狗
+ * @since 2019-04-20 12:15
+ */
+@Data
+@ApiModel(value="通用状态请求类", description="通用状态请求类")
+@AllArgsConstructor
+@NoArgsConstructor
+public class BaseStateReqDTO extends BaseDTO {
+
+
+    @ApiModelProperty(value = "要修改对象的ID列表", required=true)
+    private List<String> ids;
+
+    @ApiModelProperty(value = "通用状态,0为正常,1为禁用", required=true)
+    private Integer state;
+}

+ 23 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseTokenReqDTO.java

@@ -0,0 +1,23 @@
+package com.yf.boot.base.api.api.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * <p>
+ * token通用请求类
+ * </p>
+ *
+ * @author 聪明笨狗
+ * @since 2019-04-20 12:15
+ */
+@Data
+@ApiModel(value="token通用请求类", description="token通用请求类")
+public class BaseTokenReqDTO extends BaseDTO {
+
+
+    @ApiModelProperty(value = "令牌", required=true)
+    private String token;
+
+}

+ 29 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BaseUserReqDTO.java

@@ -0,0 +1,29 @@
+package com.yf.boot.base.api.api.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * <p>
+ * 用户通用请求类
+ * </p>
+ *
+ * @author 聪明笨狗
+ * @since 2019-04-20 12:15
+ */
+@Data
+@ApiModel(value="用户通用请求类", description="用户通用请求类")
+@AllArgsConstructor
+@NoArgsConstructor
+public class BaseUserReqDTO extends BaseDTO {
+
+
+    @JsonIgnore
+    @ApiModelProperty(value = "用户ID用来注入用的,可以不传")
+    private String userId;
+
+}

+ 25 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/BooleanRespDTO.java

@@ -0,0 +1,25 @@
+package com.yf.boot.base.api.api.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * <p>
+ * 主键通用响应类,用于添加后返回内容
+ * </p>
+ *
+ * @author 聪明笨狗
+ * @since 2019-04-20 12:15
+ */
+@Data
+@ApiModel(value="主键通用响应类", description="主键通用响应类")
+@AllArgsConstructor
+@NoArgsConstructor
+public class BooleanRespDTO extends BaseDTO {
+
+    @ApiModelProperty(value = "主键ID", required=true)
+    private Boolean effect;
+}

+ 51 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/PagingReqDTO.java

@@ -0,0 +1,51 @@
+package com.yf.boot.base.api.api.dto;
+
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 分页查询类
+ * @param <T>
+ * @author bool
+ */
+@ApiModel(value="分页参数", description="分页参数")
+@Data
+public class PagingReqDTO<T> {
+
+
+    @ApiModelProperty(value = "当前页码", required = true, example = "1")
+    private Integer current;
+
+    @ApiModelProperty(value = "每页数量", required = true, example = "10")
+    private Integer size;
+
+    @ApiModelProperty(value = "查询参数")
+    private T params;
+
+    @ApiModelProperty(value = "排序方式")
+    private List<OrderItem> orders;
+
+    @JsonIgnore
+    @ApiModelProperty(value = "当前用户的ID")
+    private String userId;
+
+    /**
+     * 转换成MyBatis的简单分页对象
+     * @return
+     */
+    public Page toPage(){
+        Page page = new Page();
+        page.setCurrent(this.current);
+        page.setSize(this.size);
+        page.setOrders(orders);
+        return page;
+    }
+
+
+}

+ 30 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/PagingRespDTO.java

@@ -0,0 +1,30 @@
+package com.yf.boot.base.api.api.dto;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+/**
+ * 分页响应类
+ * @author bool 
+ * @date 2019-07-20 15:17
+ * @param <T>
+ */
+public class PagingRespDTO<T> extends Page<T> {
+
+    /**
+     * 获取页面总数量
+     * @return
+     */
+    @Override
+    public long getPages() {
+        if (this.getSize() == 0L) {
+            return 0L;
+        } else {
+            long pages = this.getTotal() / this.getSize();
+            if (this.getTotal() % this.getSize() != 0L) {
+                ++pages;
+            }
+            return pages;
+        }
+    }
+
+}

+ 21 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/dto/PayReqDTO.java

@@ -0,0 +1,21 @@
+package com.yf.boot.base.api.api.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * <p>
+ * 支付通用请求类
+ * </p>
+ *
+ * @author 聪明笨狗
+ * @since 2019-04-20 12:15
+ */
+@Data
+@ApiModel(value="支付通用请求类", description="支付通用请求类")
+public class PayReqDTO extends BaseDTO {
+
+    @ApiModelProperty(value = "下单成功的订单号", required=true)
+    private String orderId;
+}

+ 19 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/enums/CommonState.java

@@ -0,0 +1,19 @@
+package com.yf.boot.base.api.api.enums;
+
+/**
+ * 通用的状态枚举信息
+ *
+ * @author bool
+ * @date 2019-09-17 17:57
+ */
+public interface CommonState {
+
+    /**
+     * 普通状态,正常的
+     */
+    Integer NORMAL = 0;
+    /**
+     * 非正常状态,禁用,下架等
+     */
+    Integer ABNORMAL = 1;
+}

+ 118 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/utils/FileUtils.java

@@ -0,0 +1,118 @@
+package com.yf.boot.base.api.api.utils;
+
+import java.io.File;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * 文件工具类
+ * @author van
+ */
+public class FileUtils {
+
+    /**
+     * http头常量
+     */
+    public static final String URL_SCHEMA_HTTP = "http://";
+    public static final String URL_SCHEMA_HTTPS = "https://";
+
+    /**
+     * 获取文件大小,支持本地文件及http文件地址
+     * @param file
+     * @return
+     */
+    public static String getFileSize(String file){
+
+        // 网络地址获取文件头大小
+        if(file.toLowerCase().startsWith(URL_SCHEMA_HTTP)
+                || file.toLowerCase().startsWith(URL_SCHEMA_HTTPS)){
+            return getFileSizeFromUrl(file);
+        }
+
+        return getFileSizeFromFile(file);
+    }
+
+    /**
+     * 从http获取文件大小
+     * @param httpUrl
+     * @return
+     */
+    public static String getFileSizeFromUrl(String httpUrl){
+
+        URL url = null;
+        HttpURLConnection conn = null;
+        try {
+
+            url = new URL(httpUrl);
+            conn = (HttpURLConnection) url.openConnection();
+            //根据响应获取文件大小
+            int length =  conn.getContentLength();
+            // 获取文件大小
+            return formatFileSize(length);
+        }catch (Exception e){
+            e.printStackTrace();
+        }finally {
+            if(conn!=null){
+                conn.disconnect();
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 获取本地文件大小
+     * @param path
+     * @return
+     */
+    public static String getFileSizeFromFile(String path){
+
+        File file = new File(path);
+        if(file!=null && file.isFile()){
+           long size = file.length();
+           return formatFileSize(size);
+        }
+
+        return "";
+    }
+
+
+    /**
+     * 格式化文件大小
+     * @param data
+     * @return
+     */
+    public static String formatFileSize(long data){
+
+        if (data > 0) {
+
+            double size = data * 1d;
+
+            double kiloByte = size / 1024;
+            if (kiloByte < 1 && kiloByte > 0) {
+                //不足1K
+                return String.format("%.2fByte",size);
+            }
+            double megaByte = kiloByte / 1024;
+            if (megaByte < 1) {
+                //不足1M
+                return String.format("%.2fK", kiloByte);
+            }
+
+            double gigaByte = megaByte / 1024;
+            if (gigaByte < 1) {
+                //不足1G
+                return String.format("%.2fM", megaByte);
+            }
+
+            double teraByte = gigaByte / 1024;
+            if (teraByte < 1) {
+                //不足1T
+                return String.format("%.2fG", gigaByte);
+            }
+
+            return String.format("%.2fT", teraByte);
+        }
+        return "0K";
+    }
+
+}

+ 48 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/utils/JsonConverter.java

@@ -0,0 +1,48 @@
+package com.yf.boot.base.api.api.utils;
+
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.alibaba.fastjson.support.config.FastJsonConfig;
+import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * JSON数据转换器,用于转换返回消息的格式
+ * @author dav
+ * @date 2018/9/11 19:30
+ */
+public class JsonConverter {
+
+    /**
+     * FastJson消息转换器
+     *
+     * @return
+     */
+    public static HttpMessageConverter fastConverter() {
+        // 定义一个convert转换消息的对象
+        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
+        // 添加FastJson的配置信息
+        FastJsonConfig fastJsonConfig = new FastJsonConfig();
+        // 默认转换器
+        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,
+                SerializerFeature.WriteNullNumberAsZero,
+                SerializerFeature.MapSortField,
+                SerializerFeature.WriteNullStringAsEmpty,
+                SerializerFeature.DisableCircularReferenceDetect,
+                SerializerFeature.WriteDateUseDateFormat,
+                SerializerFeature.WriteNullListAsEmpty);
+        fastJsonConfig.setCharset(Charset.forName("UTF-8"));
+        // 处理中文乱码问题
+        List<MediaType> fastMediaTypes = new ArrayList<>();
+        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
+        fastConverter.setSupportedMediaTypes(fastMediaTypes);
+        // 在convert中添加配置信息
+        fastConverter.setFastJsonConfig(fastJsonConfig);
+
+        return fastConverter;
+    }
+}

+ 40 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/utils/MsgUtils.java

@@ -0,0 +1,40 @@
+package com.yf.boot.base.api.api.utils;
+
+import com.alibaba.fastjson.JSON;
+
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * HTTP消息工具类,用于在接口错误时,回写一些数据
+ * @author bool 
+ * @date 2018/11/22 10:58
+ */
+public class MsgUtils {
+
+    /**
+     * 写错误消息
+     * @param response
+     * @param code
+     * @param msg
+     */
+    public static void writeMessage(ServletResponse response, int code, String msg){
+        Map<String,Object> map = new HashMap<>(16);
+        map.put("msg", msg);
+        map.put("code", code);
+
+        String res = JSON.toJSONString(map);
+        PrintWriter writer;
+
+        try {
+            writer = response.getWriter();
+            writer.write(res);
+            writer.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 53 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/api/utils/SignUtils.java

@@ -0,0 +1,53 @@
+package com.yf.boot.base.api.api.utils;
+
+import com.yf.boot.base.api.utils.file.MD5Util;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 签名生成工具,用于产生数据的签名和回签
+ *
+ * @author dav
+ * @date 2018/11/24 11:51
+ */
+@Log4j2
+public class SignUtils {
+
+    /**
+     * app+version为签名的key,作为后端的签名使用
+     */
+    private static final Map<String, String> SECRETS = new HashMap<String, String>() {{
+        //通用接口签名
+        put("fomille-1000", "o0tUGNebDK1ApPFn9Hn75TRGCy1yugRH");
+    }};
+
+
+    /**
+     * 参数签名算法,对获取的参数进行签名校验,以及对返回的参数进行回签校验
+     * @param timestamp
+     * @return
+     */
+    public static boolean checkSign(String app, String version, String sign, String timestamp) {
+
+        //获得秘钥
+        String secret = SECRETS.get(app + "-" + version);
+        //没有秘钥、非法访问
+        if (StringUtils.isBlank(secret)) {
+            return false;
+        }
+
+        StringBuffer sb = new StringBuffer(app)
+                .append("&").append(version)
+                .append("&").append(secret)
+                .append("&").append(timestamp);
+
+        log.info("++++++++++sign str is:"+sb.toString());
+        String mySign = MD5Util.MD5(sb.toString());
+        log.info("++++++++++true sign is:"+mySign);
+        return mySign.equals(sign);
+    }
+
+}

+ 59 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/exception/ServiceException.java

@@ -0,0 +1,59 @@
+package com.yf.boot.base.api.exception;
+
+import com.yf.boot.base.api.api.ApiError;
+import com.yf.boot.base.api.api.ApiRest;
+import lombok.Data;
+
+
+/**
+ * 通用异常处理类
+ * @author bool
+ */
+@Data
+public class ServiceException extends RuntimeException{
+
+    /**
+     * 错误码
+     */
+    private Integer code;
+
+    /**
+     * 错误消息
+     */
+    private String msg;
+
+
+    /**
+     * 从结果初始化
+     * @param apiRest
+     */
+    public ServiceException(ApiRest apiRest){
+        this.code = apiRest.getCode();
+        this.msg = apiRest.getMsg();
+    }
+
+    /**
+     * 从枚举中获取参数
+     * @param apiError
+     */
+    public ServiceException(ApiError apiError){
+        this.code = apiError.getCode();
+        this.msg = apiError.msg;
+    }
+
+    /**
+     * 通用的错误信息
+     * @param msg
+     */
+    public ServiceException(String msg){
+        this.code = 1;
+        this.msg = msg;
+    }
+
+
+    @Override
+    public String getMessage(){
+        return this.msg;
+    }
+
+}

+ 84 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/exception/ServiceExceptionHandler.java

@@ -0,0 +1,84 @@
+package com.yf.boot.base.api.exception;
+
+import com.yf.boot.base.api.api.ApiRest;
+import org.apache.shiro.authz.AuthorizationException;
+import org.springframework.http.HttpStatus;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * 统一异常处理类
+ * @author bool
+ * @date 2019-06-21 19:27
+ */
+@RestControllerAdvice
+public class ServiceExceptionHandler {
+
+    /**
+     * 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器
+     * @param binder
+     */
+    @InitBinder
+    public void initWebBinder(WebDataBinder binder){
+
+    }
+
+    /**
+     * 把值绑定到Model中,使全局@RequestMapping可以获取到该值
+     * @param model
+     */
+    @ModelAttribute
+    public void addAttribute(Model model) {
+
+    }
+
+    /**
+     * 捕获ServiceException
+     * @param e
+     * @return
+     */
+    @ExceptionHandler({ServiceException.class})
+    @ResponseStatus(HttpStatus.OK)
+    public ApiRest serviceExceptionHandler(ServiceException e) {
+        return new ApiRest(e);
+    }
+
+
+    /**
+     * Shiro异常
+     * @param e
+     * @return
+     */
+    @ExceptionHandler({AuthorizationException.class})
+    @ResponseStatus(HttpStatus.OK)
+    public ApiRest shiroExceptionHandler(AuthorizationException e) {
+        ApiRest rest = new ApiRest();
+        rest.setMsg("您无权执行此操作,请联系管理员!");
+        rest.setCode(-1);
+        return rest;
+    }
+
+
+    /**
+     * 处理数据校验返回
+     * @param e
+     * @return
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    @ResponseStatus(HttpStatus.OK)
+    public ApiRest HandleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+
+        ApiRest apiRest = new ApiRest();
+        apiRest.setCode(1);
+        apiRest.setMsg(e.getBindingResult().getFieldError().getDefaultMessage());
+        return apiRest;
+    }
+
+
+}

+ 59 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/BeanMapper.java

@@ -0,0 +1,59 @@
+package com.yf.boot.base.api.utils;
+
+import org.dozer.DozerBeanMapper;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+
+/**
+ * 简单封装Dozer, 实现深度转换Bean<->Bean的Mapper.实现:
+ *
+ * 1. 持有Mapper的单例.
+ * 2. 返回值类型转换.
+ * 3. 批量转换Collection中的所有对象.
+ * 4. 区分创建新的B对象与将对象A值复制到已存在的B对象两种函数.
+ *
+ */
+public class BeanMapper {
+
+    /**
+     * 持有Dozer单例, 避免重复创建DozerMapper消耗资源.
+     */
+    private static DozerBeanMapper dozerBeanMapper = new DozerBeanMapper();
+
+    /**
+     * 基于Dozer转换对象的类型.
+     */
+    public static <T> T map(Object source, Class<T> destinationClass) {
+        return dozerBeanMapper.map(source, destinationClass);
+    }
+
+    /**
+     * 基于Dozer转换Collection中对象的类型.
+     */
+    public static <T> List<T> mapList(Iterable<?> sourceList, Class<T> destinationClass) {
+        List<T> destinationList = new ArrayList();
+        for (Object sourceObject : sourceList) {
+            T destinationObject = dozerBeanMapper.map(sourceObject, destinationClass);
+            destinationList.add(destinationObject);
+        }
+        return destinationList;
+    }
+
+    /**
+     * 基于Dozer将对象A的值拷贝到对象B中.
+     */
+    public static void copy(Object source, Object destinationObject) {
+        if(source!=null) {
+            dozerBeanMapper.map(source, destinationObject);
+        }
+    }
+
+    public static <T, S> List<T> mapList(Collection<S> source, Function<? super S, ? extends T> mapper) {
+        return source.stream().map(mapper).collect(Collectors.toList());
+    }
+}

+ 122 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/DateUtils.java

@@ -0,0 +1,122 @@
+package com.yf.boot.base.api.utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+/**
+ * 日期处理工具类
+ * ClassName: DateUtils <br/>
+ * date: 2018年12月13日 下午6:34:02 <br/>
+ *
+ * @author Bool
+ */
+public class DateUtils {
+
+    /**
+     * calcExpDays:计算某个日期与当前日期相差的天数,如果计算的日期大于现在时间,将返回负数;否则返回正数 <br/>
+     *
+     * @param userCreateTime
+     * @return
+     * @author Bool
+     * @since JDK 1.6
+     */
+    public static int calcExpDays(Date userCreateTime) {
+
+        Calendar start = Calendar.getInstance();
+        start.setTime(userCreateTime);
+
+        Calendar now = Calendar.getInstance();
+        now.setTime(new Date());
+
+        long l = now.getTimeInMillis() - start.getTimeInMillis();
+        int days = new Long(l / (1000 * 60 * 60 * 24)).intValue();
+        return days;
+    }
+
+
+    /**
+     * dateNow:获取当前时间的字符串格式,根据传入的格式化来展示. <br/>
+     *
+     * @param format 日期格式化
+     * @return
+     * @author Bool
+     */
+    public static String dateNow(String format) {
+        SimpleDateFormat fmt = new SimpleDateFormat(format);
+        Calendar c = new GregorianCalendar();
+        return fmt.format(c.getTime());
+    }
+
+    /**
+     * formatDate:格式化日期,返回指定的格式 <br/>
+     *
+     * @param time
+     * @param format
+     * @return
+     * @author Bool
+     */
+    public static String formatDate(Date time, String format) {
+        SimpleDateFormat fmt = new SimpleDateFormat(format);
+        return fmt.format(time.getTime());
+    }
+
+
+    /**
+     * parseDate:将字符串转换成日期,使用:yyyy-MM-dd HH:mm:ss 来格式化
+     *
+     * @param date
+     * @return
+     * @author Bool
+     */
+    public static Date parseDate(String date) {
+        return parseDate(date, "yyyy-MM-dd HH:mm:ss");
+    }
+
+
+    /**
+     * parseDate:将字符串转换成日期,使用指定格式化来格式化
+     *
+     * @param date
+     * @param pattern
+     * @return
+     * @author Bool
+     */
+    public static Date parseDate(String date, String pattern) {
+
+        if (pattern == null) {
+            pattern = "yyyy-MM-dd HH:mm:ss";
+        }
+
+        SimpleDateFormat fmt = new SimpleDateFormat(pattern);
+
+        try {
+
+            return fmt.parse(date);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+        return null;
+
+    }
+
+    /**
+     * 获取一天最后时刻,用于当日有效的数据
+     *
+     * @return
+     */
+    public static Date endTimeOfDay() {
+        Calendar cl = Calendar.getInstance();
+        cl.setTimeInMillis(System.currentTimeMillis());
+        cl.set(Calendar.HOUR_OF_DAY, 23);
+        cl.set(Calendar.MINUTE, 59);
+        cl.set(Calendar.SECOND, 59);
+
+        System.out.println(DateUtils.formatDate(cl.getTime(), "yyyy-MM-dd HH:mm:ss"));
+
+        return cl.getTime();
+    }
+
+
+}

+ 267 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/DecimalUtils.java

@@ -0,0 +1,267 @@
+package com.yf.boot.base.api.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+/**
+ * Decimal相关的工具类,用于一些常规的比较
+ *
+ * @author Van
+ * @date 2019/1/22 16:39
+ */
+@Slf4j
+public class DecimalUtils {
+
+    private static final BigDecimal ZERO = new BigDecimal(0);
+
+    /**
+     * 返回一个值为0的BigDecimal
+     */
+    public static BigDecimal zero() {
+        return ZERO;
+    }
+
+    /**
+     * 如果参数a>b,则返回true,否则返回false
+     */
+    public static boolean gt(BigDecimal arg0, BigDecimal arg1) {
+        if (arg0 == null || arg1 == null) {
+            warnValue("gt", arg0, arg1);
+            return false;
+        }
+        return arg0.compareTo(arg1) > 0;
+    }
+
+    /**
+     * 如果参数a>b,则返回true,否则返回false
+     */
+    public static boolean gt(BigDecimal arg0, Double arg1) {
+        return gt(arg0, arg1 == null ? null : new BigDecimal(arg1.toString()));
+    }
+
+    /**
+     * 如果参数a>b,则返回true,否则返回false
+     */
+    public static boolean gt(BigDecimal arg0, Integer arg1) {
+        return gt(arg0, arg1 == null ? null : new BigDecimal(arg1));
+    }
+
+    /**
+     * 如果参数a>=b,则返回true,否则返回false
+     */
+    public static boolean ge(BigDecimal arg0, BigDecimal arg1) {
+        if (arg0 == null || arg1 == null) {
+            warnValue("ge", arg0, arg1);
+            return false;
+        }
+        return arg0.compareTo(arg1) >= 0;
+    }
+
+    /**
+     * 如果参数a>=b,则返回true,否则返回false
+     */
+    public static boolean ge(BigDecimal arg0, Double arg1) {
+        return gt(arg0, arg1 == null ? null : new BigDecimal(arg1.toString()));
+    }
+
+    /**
+     * 如果参数a<b,则返回true,否则返回false
+     */
+    public static boolean lt(BigDecimal arg0, BigDecimal arg1) {
+        if (arg0 == null || arg1 == null) {
+            warnValue("lt", arg0, arg1);
+            return false;
+        }
+        return arg0.compareTo(arg1) < 0;
+    }
+
+    /**
+     * 如果参数a<b,则返回true,否则返回false
+     */
+    public static boolean lt(BigDecimal arg0, Double arg1) {
+        return lt(arg0, arg1 == null ? null : new BigDecimal(arg1.toString()));
+    }
+
+    /**
+     * 如果参数a<=b,则返回true,否则返回false
+     */
+    public static boolean le(BigDecimal arg0, BigDecimal arg1) {
+        if (arg0 == null || arg1 == null) {
+            warnValue("le", arg0, arg1);
+            return false;
+        }
+        return arg0.compareTo(arg1) <= 0;
+    }
+
+    /**
+     * 如果参数a<=b,则返回true,否则返回false
+     */
+    public static boolean le(BigDecimal arg0, Double arg1) {
+        return lt(arg0, arg1 == null ? null : new BigDecimal(arg1.toString()));
+    }
+
+    /**
+     * 比较两个BigDecimal值是否一致
+     */
+    public static boolean eq(BigDecimal arg0, BigDecimal arg1) {
+        if (arg0 == null || arg1 == null) {
+            warnValue("eq", arg0, arg1);
+            return false;
+        }
+        return arg0.compareTo(arg1) == 0;
+    }
+
+    /**
+     * 该值是否是0
+     */
+    public static boolean isZero(BigDecimal value) {
+        return eq(value, ZERO);
+    }
+
+    /**
+     * 该值是否小于0
+     */
+    public static boolean ltZero(BigDecimal value) {
+        return lt(value, ZERO);
+    }
+
+    /**
+     * 改值是否大于0
+     */
+    public static boolean gtZero(BigDecimal value) {
+        return gt(value, ZERO);
+    }
+
+    /**
+     * 为空或者0
+     */
+    public static boolean nullOrZero(BigDecimal value) {
+        return value == null || isZero(value);
+    }
+
+    /**
+     * 加
+     */
+    public static BigDecimal add(BigDecimal arg0, BigDecimal arg1) {
+        if (arg0 == null && arg1 == null) {
+            warnNull("divide", "arg0, arg1");
+            return ZERO;
+        }
+        if (arg0 == null) {
+            warnNull("divide", "arg0");
+            return arg1;
+        }
+        if (arg1 == null) {
+            warnNull("divide", "arg1");
+            return arg0;
+        }
+        return arg0.add(arg1);
+    }
+
+    /**
+     * 除
+     */
+    public static BigDecimal divide(BigDecimal arg0, BigDecimal arg1) {
+        if (arg0 == null && arg1 == null) {
+            warnNull("divide", "arg0, arg1");
+            return ZERO;
+        }
+        if (arg0 == null) {
+            warnNull("divide", "arg0");
+            return ZERO;
+        }
+        if (arg1 == null) {
+            warnNull("divide", "arg1");
+            return arg0;
+        }
+        return arg0.divide(arg1,10 ,RoundingMode.DOWN);
+    }
+
+    /**
+     * 除
+     */
+    public static BigDecimal divide(BigDecimal arg0, Integer arg1) {
+        return divide(arg0, arg1 == null ? null : new BigDecimal(arg1));
+    }
+
+    /**
+     * 除
+     */
+    public static BigDecimal divide(BigDecimal arg0, Double arg1) {
+        return divide(arg0, arg1 == null ? null : new BigDecimal(arg1.toString()));
+    }
+
+    /**
+     * A*B 乘法
+     */
+    public static BigDecimal multiply(BigDecimal arg0, BigDecimal arg1) {
+        if (arg0 == null && arg1 == null) {
+            warnNull("multiply", "arg0, arg1");
+            return ZERO;
+        }
+        if (arg0 == null) {
+            warnNull("multiply", "arg0");
+            return arg1;
+        }
+        if (arg1 == null) {
+            warnNull("multiply", "arg1");
+            return arg0;
+        }
+        return arg0.multiply(arg1);
+    }
+
+    /**
+     * A*B 乘法
+     */
+    public static BigDecimal multiply(BigDecimal arg0, Integer arg1) {
+        return multiply(arg0, arg1 == null ? null : new BigDecimal(arg1));
+    }
+
+    /**
+     * 减
+     */
+    public static BigDecimal subtract(BigDecimal arg0, BigDecimal arg1) {
+        if (arg0 == null && arg1 == null) {
+            warnNull("subtract", "arg0, arg1");
+            return ZERO;
+        }
+        if (arg1 == null) {
+            warnNull("subtract", "arg1");
+            return arg0;
+        }
+        if (arg0 == null) {
+            warnNull("subtract", "arg0");
+            arg0 = ZERO;
+        }
+        return arg0.subtract(arg1);
+    }
+
+    /**
+     * 空日志警告
+     *
+     * @param methodName 方法名称
+     * @param parmName   参数名称
+     */
+    private static void warnNull(String methodName, String parmName) {
+        log.warn("DecimalUtils {} {} is null", methodName, parmName);
+    }
+
+    /**
+     * 空日志警告
+     *
+     * @param methodName 方法名称
+     * @param value      参数
+     */
+    private static void warnValue(String methodName, BigDecimal... value) {
+        log.warn("DecimalUtils {} parameter(s) {}", methodName, value);
+    }
+
+    /**
+     * 合法化 如果为null则返回0
+     */
+    public static BigDecimal legal(BigDecimal value) {
+        return value == null ? ZERO : value;
+    }
+}

+ 65 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/IpUtils.java

@@ -0,0 +1,65 @@
+package com.yf.boot.base.api.utils;
+
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * IP获取工具类,用户获取网络请求过来的真实IP
+ * ClassName: IpUtils <br/>
+ * date: 2018年2月13日 下午7:27:52 <br/>
+ *
+ * @author Bool
+ * @version
+ */
+public class IpUtils {
+
+	
+	/**
+	 *
+	 * getClientIp:通过请求获取客户端的真实IP地址
+	 * @author Bool
+	 * @param request
+	 * @return
+	 */
+	public static String extractClientIp(HttpServletRequest request) {
+
+		String ip = null;
+
+		//X-Forwarded-For:Squid 服务代理
+		String ipAddresses = request.getHeader("X-Forwarded-For");
+
+		if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
+			//Proxy-Client-IP:apache 服务代理
+			ipAddresses = request.getHeader("Proxy-Client-IP");
+		}
+
+		if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
+			//WL-Proxy-Client-IP:weblogic 服务代理
+			ipAddresses = request.getHeader("WL-Proxy-Client-IP");
+		}
+
+		if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
+			//HTTP_CLIENT_IP:有些代理服务器
+			ipAddresses = request.getHeader("HTTP_CLIENT_IP");
+		}
+
+		if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
+			//X-Real-IP:nginx服务代理
+			ipAddresses = request.getHeader("X-Real-IP");
+		}
+
+		//有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
+		if (ipAddresses != null && ipAddresses.length() != 0) {
+			ip = ipAddresses.split(",")[0];
+		}
+
+		//还是不能获取到,最后再通过request.getRemoteAddr();获取
+		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
+			ip = request.getRemoteAddr();
+		}
+
+		return ip;
+	}
+
+
+}

+ 69 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/Md5Util.java

@@ -0,0 +1,69 @@
+package com.yf.boot.base.api.utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+
+/**
+ * MD5工具类
+ * ClassName: Md5Util <br/>
+ * date: 2018年1月13日 下午6:54:53 <br/>
+ *
+ * @author Bool
+ * @version
+ */
+public class Md5Util {
+
+	
+	/**
+	 * 简单MD5
+	 * @param str
+	 * @return
+	 */
+	public static String md5(String str) {
+
+		try {
+   		 	MessageDigest md = MessageDigest.getInstance("md5");
+   	        byte[] array = md.digest(str.getBytes(StandardCharsets.UTF_8));
+   	        StringBuilder sb = new StringBuilder();
+   	        for (byte item : array) {
+   	            sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
+   	        }
+   	        return sb.toString();
+	   	}catch(Exception e) {
+	   		 return null;
+	   	}
+	}
+
+	/**
+	 * 获得文件的MD5值
+	 * @param file
+	 * @return
+	 */
+	public static String getFileMd5(File file) {
+		if (!file.isFile()) {
+			return null;
+		}
+		MessageDigest digest = null;
+		FileInputStream in = null;
+		byte [] buffer = new byte[1024];
+		int len;
+		try {
+			digest = MessageDigest.getInstance("md5");
+			in = new FileInputStream(file);
+			while ((len = in.read(buffer, 0, 1024)) != -1) {
+				digest.update(buffer, 0, len);
+			}
+			in.close();
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+		BigInteger bigInt = new BigInteger(1, digest.digest());
+		return bigInt.toString(16);
+	}
+
+}

+ 324 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/Reflections.java

@@ -0,0 +1,324 @@
+/**
+ * Copyright (c) 2005-2012 springside.org.cn
+ */
+package com.yf.boot.base.api.utils;
+
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.springframework.util.Assert;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 反射工具类.
+ * 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
+ * @author calvin
+ * @version 2016-01-15
+ */
+@Log4j2
+public class Reflections {
+	
+	private static final String SETTER_PREFIX = "set";
+
+	private static final String GETTER_PREFIX = "get";
+
+	private static final String CGLIB_CLASS_SEPARATOR = "$$";
+
+
+	/**
+	 * 获取类的所有属性,包括父类
+	 *
+	 * @param object
+	 * @return
+	 */
+	public static Field[] getAllFields(Object object) {
+		Class<?> clazz = object.getClass();
+		List<Field> fieldList = new ArrayList<>();
+		while (clazz != null) {
+			fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
+			clazz = clazz.getSuperclass();
+		}
+		Field[] fields = new Field[fieldList.size()];
+		fieldList.toArray(fields);
+		return fields;
+	}
+
+
+	/**
+	 * 调用Getter方法.
+	 * 支持多级,如:对象名.对象名.方法
+	 */
+	public static Object invokeGetter(Object obj, String propertyName) {
+		Object object = obj;
+		for (String name : StringUtils.split(propertyName, ".")){
+			String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
+			object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
+		}
+		return object;
+	}
+
+	/**
+	 * 调用Setter方法, 仅匹配方法名。
+	 * 支持多级,如:对象名.对象名.方法
+	 */
+	public static void invokeSetter(Object obj, String propertyName, Object value) {
+		Object object = obj;
+		String[] names = StringUtils.split(propertyName, ".");
+		for (int i=0; i<names.length; i++){
+			if(i<names.length-1){
+				String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
+				object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
+			}else{
+				String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
+				invokeMethodByName(object, setterMethodName, new Object[] { value });
+			}
+		}
+	}
+
+	/**
+	 * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
+	 */
+	public static Object getFieldValue(final Object obj, final String fieldName) {
+		Field field = getAccessibleField(obj, fieldName);
+
+		if (field == null) {
+			throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
+		}
+
+		Object result = null;
+		try {
+			result = field.get(obj);
+		} catch (IllegalAccessException e) {
+			log.error("不可能抛出的异常{}", e.getMessage());
+		}
+		return result;
+	}
+
+	/**
+	 * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
+	 */
+	public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
+		Field field = getAccessibleField(obj, fieldName);
+
+		if (field == null) {
+			throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
+		}
+
+		try {
+			field.set(obj, value);
+		} catch (IllegalAccessException e) {
+			log.error("不可能抛出的异常:{}", e.getMessage());
+		}
+	}
+
+	/**
+	 * 直接调用对象方法, 无视private/protected修饰符.
+	 * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用.
+	 * 同时匹配方法名+参数类型,
+	 */
+	public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
+                                      final Object[] args) {
+		Method method = getAccessibleMethod(obj, methodName, parameterTypes);
+		if (method == null) {
+			throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
+		}
+
+		try {
+			return method.invoke(obj, args);
+		} catch (Exception e) {
+			throw convertReflectionExceptionToUnchecked(e);
+		}
+	}
+
+	/**
+	 * 直接调用对象方法, 无视private/protected修饰符,
+	 * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
+	 * 只匹配函数名,如果有多个同名函数调用第一个。
+	 */
+	public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) {
+		Method method = getAccessibleMethodByName(obj, methodName);
+		if (method == null) {
+			throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
+		}
+
+		try {
+			return method.invoke(obj, args);
+		} catch (Exception e) {
+			throw convertReflectionExceptionToUnchecked(e);
+		}
+	}
+
+	/**
+	 * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
+	 * 
+	 * 如向上转型到Object仍无法找到, 返回null.
+	 */
+	public static Field getAccessibleField(final Object obj, final String fieldName) {
+		Validate.notNull(obj, "object can't be null");
+		Validate.notBlank(fieldName, "fieldName can't be blank");
+		for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
+			try {
+				Field field = superClass.getDeclaredField(fieldName);
+				makeAccessible(field);
+				return field;
+			} catch (NoSuchFieldException e) {//NOSONAR
+				// Field不在当前类定义,继续向上转型
+				continue;// new add
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
+	 * 如向上转型到Object仍无法找到, 返回null.
+	 * 匹配函数名+参数类型。
+	 * 
+	 * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
+	 */
+	public static Method getAccessibleMethod(final Object obj, final String methodName,
+                                             final Class<?>... parameterTypes) {
+		Validate.notNull(obj, "object can't be null");
+		Validate.notBlank(methodName, "methodName can't be blank");
+
+		for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
+			try {
+				Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
+				makeAccessible(method);
+				return method;
+			} catch (NoSuchMethodException e) {
+				// Method不在当前类定义,继续向上转型
+				continue;// new add
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
+	 * 如向上转型到Object仍无法找到, 返回null.
+	 * 只匹配函数名。
+	 * 
+	 * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
+	 */
+	public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
+		Validate.notNull(obj, "object can't be null");
+		Validate.notBlank(methodName, "methodName can't be blank");
+
+		for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
+			Method[] methods = searchType.getDeclaredMethods();
+			for (Method method : methods) {
+				if (method.getName().equals(methodName)) {
+					makeAccessible(method);
+					return method;
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
+	 */
+	public static void makeAccessible(Method method) {
+		if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
+				&& !method.isAccessible()) {
+			method.setAccessible(true);
+		}
+	}
+
+	/**
+	 * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
+	 */
+	public static void makeAccessible(Field field) {
+		if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
+				.isFinal(field.getModifiers())) && !field.isAccessible()) {
+			field.setAccessible(true);
+		}
+	}
+
+	/**
+	 * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处
+	 * 如无法找到, 返回Object.class.
+	 * eg.
+	 * public UserDao extends HibernateDao<User>
+	 *
+	 * @param clazz The class to introspect
+	 * @return the first generic declaration, or Object.class if cannot be determined
+	 */
+	@SuppressWarnings("unchecked")
+	public static <T> Class<T> getClassGenricType(final Class clazz) {
+		return getClassGenricType(clazz, 0);
+	}
+
+	/**
+	 * 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
+	 * 如无法找到, 返回Object.class.
+	 * 
+	 * 如public UserDao extends HibernateDao<User,Long>
+	 *
+	 * @param clazz clazz The class to introspect
+	 * @param index the Index of the generic ddeclaration,start from 0.
+	 * @return the index generic declaration, or Object.class if cannot be determined
+	 */
+	public static Class getClassGenricType(final Class clazz, final int index) {
+
+		Type genType = clazz.getGenericSuperclass();
+
+		if (!(genType instanceof ParameterizedType)) {
+			log.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
+			return Object.class;
+		}
+
+		Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
+
+		if (index >= params.length || index < 0) {
+			log.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
+					+ params.length);
+			return Object.class;
+		}
+		if (!(params[index] instanceof Class)) {
+			log.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
+			return Object.class;
+		}
+
+		return (Class) params[index];
+	}
+	
+	public static Class<?> getUserClass(Object instance) {
+		Assert.notNull(instance, "Instance must not be null");
+		Class clazz = instance.getClass();
+		if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
+			Class<?> superClass = clazz.getSuperclass();
+			if (superClass != null && !Object.class.equals(superClass)) {
+				return superClass;
+			}
+		}
+		return clazz;
+
+	}
+	
+	/**
+	 * 将反射时的checked exception转换为unchecked exception.
+	 */
+	public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
+		if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
+				|| e instanceof NoSuchMethodException) {
+			return new IllegalArgumentException(e);
+		} else if (e instanceof InvocationTargetException) {
+			return new RuntimeException(((InvocationTargetException) e).getTargetException());
+		} else if (e instanceof RuntimeException) {
+			return (RuntimeException) e;
+		}
+		return new RuntimeException("Unexpected Checked Exception.", e);
+	}
+}

+ 32 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/SpringUtils.java

@@ -0,0 +1,32 @@
+package com.yf.boot.base.api.utils;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Spring获取工具
+ *
+ * @author bool
+ * @date 2019-12-09 15:55
+ */
+@Component
+public class SpringUtils implements ApplicationContextAware {
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext context) throws BeansException {
+        applicationContext = context;
+    }
+
+    public static <T> T getBean(Class<T> tClass) {
+        return applicationContext.getBean(tClass);
+    }
+
+    public static <T> T getBean(String name, Class<T> type) {
+        return applicationContext.getBean(name, type);
+    }
+
+}

+ 100 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/download/DownloadUtil.java

@@ -0,0 +1,100 @@
+package com.yf.boot.base.api.utils.download;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+
+/**
+ * 下载工具类,用于将网络文件本地化,简单的下载,没有断点下载和重试机制;
+ * @author bool 
+ * @date 2018/7/30 09:16
+ */
+public class DownloadUtil {
+
+    /**
+     * 日志
+     */
+    private static Logger logger = LoggerFactory.getLogger("downloader");
+
+    /**
+     * 将Nginx配置文件本地化
+     * @param url
+     * @param dir
+     * @return
+     * @throws Exception
+     */
+    public static String download(String url, String dir, String fileName) throws Exception {
+        // 创建HttpClient
+        CloseableHttpClient client = HttpClients.createDefault();
+
+        File dirFile = new File(dir);
+        if(!dirFile.exists()){
+            dirFile.mkdirs();
+        }
+
+        //获得文件名
+        if(StringUtils.isEmpty(fileName)){
+            fileName = extractUrlFileName(url);
+        }
+        try {
+            HttpGet httpGet = new HttpGet(url);
+            logger.info("executing request: " + httpGet.getURI());
+            HttpResponse response = client.execute(httpGet);
+            File targetFile = new File(dir, fileName);
+            if(targetFile.exists()){
+                targetFile.delete();
+            }
+            FileOutputStream fos = new FileOutputStream(targetFile);
+            // 得到网络资源的字节数组,并写入文件
+            HttpEntity entity = response.getEntity();
+            if (entity != null) {
+                InputStream is = entity.getContent();
+                try {
+                    byte b[] = new byte[1024];
+                    int j;
+                    while ((j = is.read(b)) != -1) {
+                        fos.write(b, 0, j);
+                    }
+                    fos.flush();
+                    fos.close();
+                } catch (Exception ex) {
+                    throw ex;
+                } finally {
+                    is.close();
+                }
+                if (targetFile.exists()) {
+                    return targetFile.getCanonicalPath();
+                }
+            }
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+        } finally {
+            client.close();
+        }
+        return null;
+    }
+
+
+    /**
+     * 从URL中提取文件名称
+     * @param url
+     * @return
+     */
+    private static String extractUrlFileName(String url) {
+        int index = url.lastIndexOf("/");
+        if (index != -1) {
+            return url.substring(index+1);
+        }
+        return null;
+    }
+
+}

+ 307 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/download/Downloader.java

@@ -0,0 +1,307 @@
+package com.yf.boot.base.api.utils.download;
+
+import com.alibaba.fastjson.JSON;
+import com.yf.boot.base.api.utils.download.temp.DownloadTemp;
+import com.yf.boot.base.api.utils.download.temp.DownloadTempThread;
+import com.yf.boot.base.api.utils.download.thread.DownloadThread;
+import com.yf.boot.base.api.utils.file.MD5Util;
+import com.yf.boot.base.api.utils.file.TextFileUtils;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * JAVA多线程断点下载工具,支持多线程下载和断点续传
+ *
+ * @author bool
+ * @date 2018/8/23 08:49
+ */
+public class Downloader {
+
+    /**
+     * 正确响应
+     */
+    private static final int RESPONSE_OK = 200;
+
+    /**
+     * 临时文件的后缀名
+     */
+    private static final String TEMP_FILE_SUFFIX = ".jd";
+
+    /**
+     * 同时下载的线程数
+     */
+    private int threadCount = 5;
+
+    /**
+     * 日志相关的内容
+     */
+    private Logger logger = LoggerFactory.getLogger("downloader");
+
+    /**
+     * 总大小
+     */
+    private long fileLength;
+
+    /**
+     * 已下载的大小
+     */
+    private long downloaded;
+
+    /**
+     * 下载文件的URL
+     */
+    private String url;
+
+    /**
+     * 保存文件的路径
+     */
+    private String dist;
+
+    /**
+     * 下载线程
+     */
+    private DownloadThread[] threads = new DownloadThread[threadCount];
+
+    /**
+     * 定时器,用于保存下载进度
+     */
+    private ScheduledExecutorService executorService;
+
+
+    /**
+     * 构造下载器,传入下载地址和文件保存路径
+     *
+     * @param url
+     * @param dist
+     */
+    public Downloader(String url, String dist) {
+        this.url = url;
+        this.dist = dist;
+    }
+
+    /**
+     * 下载文件入口,下载成功返回true
+     *
+     * @return
+     * @throws Exception
+     */
+    public void download() throws Exception {
+
+        //开启断点下载
+        if (checkNeedContinue()) {
+            this.continueDownload();
+        } else {
+            //开启全新的下载
+            this.newDownload();
+        }
+
+        //运行定时任务,保存下载进度
+        executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("scheduled-pool-%d").daemon(true).build());
+        executorService.scheduleAtFixedRate(() -> saveProcess(), 0, 800, TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * 开启全新的下载
+     *
+     * @return
+     * @throws Exception
+     */
+    private void newDownload() throws Exception {
+        //连接文件、确定文件头的大小
+        CloseableHttpClient client = HttpClients.createDefault();
+        HttpGet get = new HttpGet(url);
+        CloseableHttpResponse response = client.execute(get);
+
+        int statusCode = response.getStatusLine().getStatusCode();
+        //如果URL存在重定向,则获取重定向后的URL拿来下载
+        if (statusCode != RESPONSE_OK) {
+            throw new Exception("URL响应状态不正确:" + statusCode);
+        }
+
+        //获取文件的长度
+        fileLength = response.getEntity().getContentLength();
+
+        //每个线程下载的数据
+        long pie = fileLength / threadCount;
+        long left = fileLength % threadCount;
+
+        for (int i = 0; i < threadCount; i++) {
+
+            long start = i * pie;
+            long end = (i + 1) * pie;
+            //最后的线程连剩下的也一起下了
+            if ((i + 1) == threadCount) {
+                end += left;
+            }
+            //第二段开始索引+1
+            if (i > 0) {
+                start += 1;
+            }
+
+            logger.info("start download form bytes {0} to {1}", start, end);
+            //加入线程池下载
+            String threadName = MD5Util.MD5(url) + "-" + i;
+            DownloadThread thread = new DownloadThread(url, dist, start, end);
+            thread.setName(threadName);
+            threads[i] = thread;
+            threads[i].start();
+        }
+
+    }
+
+    /**
+     * 检查全部线程是否还是活动的,如果有一个没完成,则说明下载还在继续
+     *
+     * @return
+     */
+    public boolean isDownloading() {
+        for (DownloadThread thread : threads) {
+            if (thread.isAlive()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * 检测是否需要断点下载
+     * @return
+     */
+    public boolean checkNeedContinue() {
+        //检查是否需要断点下载
+        File tempFile = new File(dist + TEMP_FILE_SUFFIX);
+
+        //开启断点下载
+        return tempFile.exists();
+    }
+
+    /**
+     * 断点继续下载,主要根据描述文件是否存在来做区分
+     */
+    private void continueDownload() {
+
+        //开启断点下载
+        String json = TextFileUtils.readText(dist + TEMP_FILE_SUFFIX);
+        DownloadTemp info = JSON.parseObject(json, DownloadTemp.class);
+        this.fileLength = info.getFileLength();
+
+        //如果信息读取不正确或者没有要下载的线程,直接返回
+        if (info == null || info.getThreads()!=null || info.getThreads().size()==0) {
+            return;
+        }
+
+        //根据文件的存储重新设置下载线程
+        this.threadCount = info.getThreads().size();
+        this.threads = new DownloadThread[this.threadCount];
+
+        int i = 0;
+        for (DownloadTempThread t : info.getThreads()) {
+            DownloadThread thread = new DownloadThread();
+            thread.fromTemp(this.url, this.dist, t);
+            threads[i] = thread;
+            threads[i].start();
+            i++;
+        }
+
+    }
+
+
+    /**
+     * 保存下载进度
+     */
+    public void saveProcess() {
+
+        List<DownloadTempThread> records = new ArrayList<>();
+        long downloaded = 0;
+
+        for (DownloadThread thread : this.threads) {
+            //保存线程加载信息
+            records.add(thread.toTemp());
+            //累计进度
+            downloaded += thread.getLoaded();
+        }
+
+        DownloadTemp info = new DownloadTemp();
+        info.setFileLength(fileLength);
+        info.setUrl(url);
+        info.setThreads(records);
+
+        //写入临时文件以备断点下载
+        TextFileUtils.write(dist + TEMP_FILE_SUFFIX, JSON.toJSONString(info));
+
+        //已下载的数量
+        this.downloaded = downloaded;
+    }
+
+
+    /**
+     * 获取文件的大小
+     * @return
+     */
+    public long getFileLength() {
+        return fileLength;
+    }
+
+    /**
+     * 获取已下载的大小
+     * @return
+     */
+    public long getDownloaded() {
+        return downloaded;
+    }
+
+
+    /**
+     * 测试方法,用于测试下载
+     * @param args
+     * @throws Exception
+     */
+    public static void main(String[] args) throws Exception {
+
+
+        //下载参数
+//        String url = "https://cdn-file1.sitebuilding.cn/resource.zip";
+//        String dist = "/Users/bool/Desktop/resource.zip";
+
+        String url = "https://cdn-file1.sitebuilding.cn/itranslate.dmg";
+        String dist = "/Users/bool/Desktop/itranslate.dmg";
+
+
+        //开启下载
+        Downloader downloader = new Downloader(url, dist);
+        downloader.download();
+
+        //如果线程还是活动的,则计算下载进度
+        while (downloader.isDownloading()) {
+            //等待获取下载进度
+            Thread.sleep(800);
+            //计算下载进度
+            float percent = downloader.getDownloaded() * 100f / downloader.getFileLength();
+            System.out.println("+++++下载进度为:" + percent + "%");
+        }
+
+        //可以做一些文件的完整性校验
+        String md5 = MD5Util.getFileMD5(new File(dist));
+        System.out.println("+++++下载件MD5:" + md5);
+
+        //比较源文件的MD5
+        String example = "/Users/bool/Downloads/itranslate.dmg";
+        String exMD5 = MD5Util.getFileMD5(new File(example));
+        System.out.println("+++++参考件MD5:" + exMD5);
+
+    }
+}

+ 48 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/download/temp/DownloadTemp.java

@@ -0,0 +1,48 @@
+package com.yf.boot.base.api.utils.download.temp;
+
+import java.util.List;
+
+/**
+ * 下载临时文件存储,用于边下载边将已经读取的数据记录下来,保存在文件中,下次读取自动从断点下载。
+ * @author bool 
+ * @date 2018/8/24 09:02
+ */
+public class DownloadTemp {
+
+    /**
+     * 下载源URL
+     */
+    private String url;
+    /**
+     * 文件的总长度
+     */
+    private long fileLength;
+    /**
+     * 下载的线程列表
+     */
+    private List<DownloadTempThread> threads;
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public long getFileLength() {
+        return fileLength;
+    }
+
+    public void setFileLength(long fileLength) {
+        this.fileLength = fileLength;
+    }
+
+    public List<DownloadTempThread> getThreads() {
+        return threads;
+    }
+
+    public void setThreads(List<DownloadTempThread> threads) {
+        this.threads = threads;
+    }
+}

+ 58 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/download/temp/DownloadTempThread.java

@@ -0,0 +1,58 @@
+package com.yf.boot.base.api.utils.download.temp;
+
+/**
+ * 下载线程信息,临时文件的一部分,用于保存各个线程的下载情况。
+ * @author bool
+ * @date 2018/8/24 09:03
+ */
+public class DownloadTempThread {
+
+    /**
+     * 线程名称
+     */
+    private String threadName;
+    /**
+     * 跳过的字节数
+     */
+    private long skip;
+    /**
+     * 读取的字节数
+     */
+    private long pos;
+    /**
+     * 已经加载的数据,用于保存进度
+     */
+    private long loaded;
+
+    public String getThreadName() {
+        return threadName;
+    }
+
+    public void setThreadName(String threadName) {
+        this.threadName = threadName;
+    }
+
+    public long getSkip() {
+        return skip;
+    }
+
+    public void setSkip(long skip) {
+        this.skip = skip;
+    }
+
+    public long getPos() {
+        return pos;
+    }
+
+    public void setPos(long pos) {
+        this.pos = pos;
+    }
+
+    public long getLoaded() {
+        return loaded;
+    }
+
+    public void setLoaded(long loaded) {
+        this.loaded = loaded;
+    }
+}

+ 173 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/download/thread/DownloadThread.java

@@ -0,0 +1,173 @@
+package com.yf.boot.base.api.utils.download.thread;
+
+
+import com.yf.boot.base.api.utils.download.temp.DownloadTempThread;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+
+/**
+ * 文件下载线程,用于多线程下载文件。
+ * @author bool 
+ * @date 2018/8/24 09:20
+ */
+public class DownloadThread extends Thread {
+
+    /**
+     * 日志相关的内容
+     */
+    private Logger logger = LoggerFactory.getLogger("downloader");
+
+    /**
+     * 跳过的长度
+     */
+    private long skip;
+
+    /**
+     * 分段的长度
+     */
+    private long pos;
+
+    /**
+     * 已经下载的字节数,用来统计下载进度
+     */
+    private long loaded;
+
+    /**
+     * 下载地址
+     */
+    private String url;
+
+    /**
+     * 保存路径
+     */
+    private String dist;
+
+    /**
+     * 默认构造
+     */
+    public DownloadThread() {
+
+    }
+
+    /**
+     * 构造方法,输入分段信息
+     * @param skip
+     * @param pos
+     */
+    public DownloadThread(String url, String dist, long skip, long pos) {
+        this.skip = skip;
+        this.pos = pos;
+        this.url = url;
+        this.dist = dist;
+    }
+
+    @Override
+    public void run() {
+
+        String threadName = Thread.currentThread().getName();
+        logger.info("thread {0} start download", threadName);
+
+        RandomAccessFile raf;
+        //连接文件、确定文件头的大小
+        CloseableHttpClient client = HttpClients.createDefault();
+        HttpGet get = new HttpGet(this.url);
+        //获取指定的文件段
+        get.setHeader("RANGE", "bytes="+this.skip+"-"+this.pos);
+
+        try {
+            raf = new RandomAccessFile(this.dist, "rw");
+            raf.seek(this.skip);
+            CloseableHttpResponse response = client.execute(get);
+
+            //获得文件流
+            InputStream is = response.getEntity().getContent();
+
+            int len;
+            byte[] b = new byte[1024];
+            while ((len = is.read(b)) != -1) {
+                raf.write(b, 0, len);
+                // 定义已经该线程已下载的字节数
+                loaded += len;
+            }
+            is.close();
+            raf.close();
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 将当前的线程装换成可存储的临时对象,用于临时文件保存。
+     * @return
+     */
+    public DownloadTempThread toTemp(){
+
+        //保存线程加载信息
+        DownloadTempThread t = new DownloadTempThread();
+        t.setLoaded(this.loaded);
+        t.setPos(this.pos);
+        t.setSkip(this.skip);
+        t.setThreadName(this.getName());
+        return t;
+    }
+
+    /**
+     * 从临时文件中初始化线程,用于从临时文件恢复下载
+     * @param url
+     * @param dist
+     * @param t
+     */
+    public void fromTemp(String url, String dist, DownloadTempThread t){
+        this.url = url;
+        this.dist = dist;
+        this.skip = t.getSkip() + t.getLoaded();
+        this.pos = t.getPos();
+        this.loaded = t.getLoaded();
+        this.setName(t.getThreadName());
+    }
+
+
+    public long getSkip() {
+        return skip;
+    }
+
+    public void setSkip(long skip) {
+        this.skip = skip;
+    }
+
+    public long getPos() {
+        return pos;
+    }
+
+    public void setPos(long pos) {
+        this.pos = pos;
+    }
+
+    public long getLoaded() {
+        return loaded;
+    }
+
+    public void setLoaded(long loaded) {
+        this.loaded = loaded;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+}

+ 234 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/excel/ExportExcel.java

@@ -0,0 +1,234 @@
+package com.yf.boot.base.api.utils.excel;
+
+import cn.hutool.core.io.IoUtil;
+import com.yf.boot.base.api.exception.ServiceException;
+import com.yf.boot.base.api.utils.DateUtils;
+import com.yf.boot.base.api.utils.Reflections;
+import com.yf.boot.base.api.utils.excel.annotation.ExcelField;
+import com.yf.exam.modules.sys.dict.service.SysDicValueService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.*;
+
+/**
+ * excel导出工具类
+ * @author bool
+ */
+@Component
+public class ExportExcel {
+
+    @Autowired
+    private SysDicValueService sysDicValueService;
+
+    /**
+     * 导出数据
+     * @param response
+     * @param clazz 数据类型
+     * @param title 表头名称
+     * @param list 列表数据
+     */
+    public void export(HttpServletResponse response, Class clazz, String title, List list){
+
+
+        if(CollectionUtils.isEmpty(list)){
+            throw new ServiceException("没有可导出的数据,请确认!");
+        }
+
+        // 获取当前类字段
+        Field[] fields = clazz.getDeclaredFields();
+
+        // 字段列表
+        List<Field> fieldList = new ArrayList<>();
+
+        for (Field field : fields) {
+            if (!field.isAccessible()) {
+                // 关闭反射访问安全检查,为了提高速度
+                field.setAccessible(true);
+            }
+
+            // 只导出有注解的字段
+            boolean export = field.isAnnotationPresent(ExcelField.class);
+            if(!export){
+                continue;
+            }
+
+            // 加入列表
+            fieldList.add(field);
+        }
+
+
+        // 进行列排序
+        Collections.sort(fieldList, (o1, o2) -> {
+            ExcelField a1 = o1.getAnnotation(ExcelField.class);
+            ExcelField a2 = o2.getAnnotation(ExcelField.class);
+            return a1.sort() - a2.sort();
+        });
+
+        try {
+            this.write(response, fieldList, title, list);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+
+    /**
+     * 导出数据
+     * @param response
+     * @param list
+     * @throws Exception
+     */
+    private void write(HttpServletResponse response, List<Field> fieldList, String title, List list) throws IOException {
+
+        MyExcelWriter writer= MyExcelWriter.getBigWriter();
+
+        // 处理内容
+        List<Map<String,Object>> rows = this.processRows(fieldList, list);
+
+        writer.merge(rows.get(0).size() - 1, title);
+        writer.write(rows, true);
+        writer.autoSizeColumnAll();
+
+        ServletOutputStream out = response.getOutputStream();
+        response.setContentType("application/vnd.ms-excel;charset=utf-8");
+        writer.flush(out, true);
+        writer.close();
+        IoUtil.close(out);
+    }
+
+
+    /**
+     * 添加数据(通过annotation.ExportField添加数据)
+     * @return list 数据列表
+     */
+    public List<Map<String,Object>> processRows(List<Field> fieldList, List list){
+
+        List<Map<String,Object>> all = new ArrayList<>();
+        for(Object data: list){
+            Map<String, Object> row = new LinkedHashMap<>();
+            for(Field field: fieldList){
+                ExcelField ann = field.getAnnotation(ExcelField.class);
+                Object val = Reflections.invokeGetter(data, field.getName());
+
+                // 数据字典转换及相关格式化
+                String result = this.transData(ann, val);
+
+                // 统一转成文本处理
+                row.put(ann.title(), result);
+            }
+            all.add(row);
+        }
+        return all;
+    }
+
+    /**
+     * 数据字典转换
+     * @param ann
+     * @param val
+     * @return
+     */
+    private String transDict(ExcelField ann, Object val){
+
+        String table = ann.dictTable();
+        String code = ann.dictCode();
+        String text = ann.dicText();
+
+        String key = String.valueOf(val);
+        if(StringUtils.isEmpty(key)){
+            return key;
+        }
+
+        try {
+            // 数据字典表
+            if (StringUtils.isEmpty(table)) {
+                return sysDicValueService.findDictText(code, key);
+            }
+
+            // 直接查其他表
+            return sysDicValueService.findTableText(table, text, code, key);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return String.valueOf(val);
+    }
+
+
+    /**
+     * 数据处理、转换、格式化等
+     * @param ann
+     * @param val
+     * @return
+     */
+    private String transData(ExcelField ann, Object val){
+
+        // 为空直接返回空数据
+        if(val == null){
+            return "";
+        }
+
+        // 时间格式化
+        if(val instanceof java.util.Date){
+
+            String pattern = ann.pattern();
+            // 默认日期格式
+            if(StringUtils.isEmpty(pattern)){
+                pattern = "yyyy-MM-dd HH:mm:ss";
+            }
+            return DateUtils.formatDate((Date)val, pattern);
+        }
+
+        // 静态过滤
+        String filter = ann.filter();
+
+        // 处理静态映射
+        if(!StringUtils.isEmpty(filter)){
+            return this.transFilter(val, filter);
+        }
+
+        // 处理数据字典
+        String dic = ann.dictCode();
+        if(!StringUtils.isEmpty(dic)){
+            return this.transDict(ann, val);
+        }
+
+        // 返回空字符
+        return String.valueOf(val);
+    }
+
+    /**
+     * 静态数据转换,如:1=男,0=女
+     * @param val
+     * @param filter
+     * @return
+     */
+    private String transFilter(Object val, String filter){
+
+        String [] arr = filter.split(",");
+
+        if(arr.length > 0) {
+
+            for (String item : arr) {
+                String[] arr1 = item.split("=");
+
+                // 空用null表示
+                if (((val == null || "".equals(String.valueOf(val))) && "null".equals(arr1[0])) || String.valueOf(val).equals(arr1[0])) {
+                    return arr1[1];
+                }
+            }
+        }
+
+        // 返回未翻译的值
+        return String.valueOf(val);
+    }
+
+
+}

+ 258 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/excel/ImportExcel.java

@@ -0,0 +1,258 @@
+package com.yf.boot.base.api.utils.excel;
+
+import cn.hutool.poi.excel.ExcelUtil;
+import cn.hutool.poi.excel.sax.handler.RowHandler;
+import com.alibaba.fastjson.JSONObject;
+import com.yf.boot.base.api.exception.ServiceException;
+import com.yf.boot.base.api.utils.excel.annotation.ExcelField;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 导入Excel表格,支持.xls和.xlsx,基于hutool进行封装的
+ *
+ * @author bool
+ */
+@Log4j2
+public class ImportExcel {
+
+    /**
+     * 标题行号
+     */
+    private int headerNum;
+
+    /**
+     * 名称对应实体字段映射
+     */
+    private List<String> headerList = new ArrayList<>();
+
+    /**
+     * 提取出的数据列表
+     */
+    private List<Map<String, Object>> dataList = new ArrayList<>();
+
+
+    /**
+     * 处理数据
+     *
+     * @return
+     */
+    private RowHandler createRowHandler() {
+        return (sheetIndex, rowIndex, rowList) -> {
+
+            // 提取表头出来
+            if (rowIndex == headerNum) {
+                for (Object obj : rowList) {
+                    String title = String.valueOf(obj);
+                    headerList.add(title);
+                }
+                return;
+            }
+
+            // 没有表头直接返回
+            if (CollectionUtils.isEmpty(headerList)) {
+                return;
+            }
+
+
+            // 对应数据成名称-值
+            Map<String, Object> map = new HashMap<>();
+            for (int i = 0; i < rowList.size(); i++) {
+                if(i >= headerList.size()){
+                    break;
+                }
+                map.put(headerList.get(i), rowList.get(i));
+            }
+
+
+            // 加入列表
+            this.addToList(map);
+
+        };
+    }
+
+    /**
+     * 将Map放入List
+     * @param map
+     */
+    private void addToList(Map<String, Object> map){
+        boolean empty = true;
+
+        // 全部key值为空,则可能是表格某个格子
+        if(map == null || map.isEmpty()){
+            return;
+        }
+
+        // 循环值
+        for(String key: map.keySet()){
+            Object val = map.get(key);
+            if(val!=null && !StringUtils.isEmpty(val.toString())){
+                empty = false;
+            }
+        }
+
+        if(!empty){
+            dataList.add(map);
+        }
+    }
+
+	/**
+	 * 读取文件,默认第二行为表头
+	 * @param file
+	 */
+	public ImportExcel(MultipartFile file){
+		// 默认从第二行开始读表头
+		this(file, 1);
+	}
+
+
+	/**
+	 * 读取文件,表头行号索引从0开始,即excel行号-1
+	 * @param file
+	 * @param headerNum
+	 */
+    public ImportExcel(MultipartFile file, int headerNum) {
+
+        // 头部行货
+        this.headerNum = headerNum;
+
+        String fileName = file.getOriginalFilename();
+        if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
+            throw new ServiceException("导入的文件格式错误,必须为.xls或.xlsx");
+        }
+
+		try {
+			ExcelUtil.readBySax(file.getInputStream(), -1, createRowHandler());
+		} catch (IOException e) {
+			e.printStackTrace();
+			throw new ServiceException("数据流读取失败!");
+		}
+	}
+
+
+	/**
+	 * 反射java类,形成一个表头名称<-->java字段的Map
+	 * @param clazz
+	 * @return
+	 */
+	private Map<String, Field> processFields(Class clazz) {
+
+        // 获取当前类字段
+        Field[] fields = clazz.getDeclaredFields();
+
+        // 标题对应注解
+        Map<String, Field> fieldMap = new HashMap<>();
+
+
+        for (Field field : fields) {
+            if (!field.isAccessible()) {
+                // 关闭反射访问安全检查,为了提高速度
+                field.setAccessible(true);
+            }
+
+            // 只导出有注解的字段
+            boolean export = field.isAnnotationPresent(ExcelField.class);
+            if (!export) {
+                continue;
+            }
+
+            // 加入列表
+            ExcelField ann = field.getAnnotation(ExcelField.class);
+            fieldMap.put(ann.title(), field);
+        }
+
+        return fieldMap;
+
+    }
+
+    /**
+     * 返回对应实体列表数据
+     * @param cls
+     * @param <E>
+     * @return
+     */
+    public <E> List<E> getDataList(Class<E> cls) {
+
+        // 名称对应关系
+        Map<String, Field> fieldMap = this.processFields(cls);
+
+        List<E> list = new ArrayList<>();
+
+        for (Map<String, Object> map : dataList) {
+
+            // 使用JSON对象进行数据接收
+            JSONObject json = new JSONObject();
+
+            for (String key : map.keySet()) {
+
+                // 丢弃不存在的映射
+                if (!fieldMap.containsKey(key)) {
+                    continue;
+                }
+
+                // 创建实例并赋值
+                Field field = fieldMap.get(key);
+
+                // 原始表格数据
+                Object val = map.get(key);
+
+                // 注解参数
+                ExcelField ann = field.getAnnotation(ExcelField.class);
+
+                // 处理静态数据过滤:此处和导出一样,过滤器需要与导出写反的,如:正常=0,禁用=1
+                this.transFilter(ann, val);
+
+                // TODO 数据字典反向,暂时不处理,就是根据数据字典标题查值
+
+                // 使用JSON转换,使用反射也可,效率问题待研究
+                json.put(field.getName(), val);
+            }
+
+            list.add(json.toJavaObject(cls));
+        }
+        return list;
+    }
+
+
+    /**
+     * 数据字典转换
+     * @param ann
+     * @param val
+     * @return
+     */
+    private Object transFilter(ExcelField ann, Object val){
+
+        String filter = ann.filter();
+
+        // 不需要处理
+        if(StringUtils.isEmpty(filter)){
+            return val;
+        }
+
+        String [] arr = filter.split(",");
+
+        if(arr.length == 0){
+            return val;
+        }
+
+        for(String item: arr){
+            String [] arr1 = item.split("=");
+            if(String.valueOf(val).equals(arr1[0])){
+                return arr1[1];
+            }
+        }
+
+        // 进行转换
+        return val;
+    }
+
+}

+ 32 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/excel/MyExcelWriter.java

@@ -0,0 +1,32 @@
+package com.yf.boot.base.api.utils.excel;
+
+import cn.hutool.core.exceptions.DependencyException;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.poi.excel.BigExcelWriter;
+import org.apache.poi.xssf.streaming.SXSSFSheet;
+
+public class MyExcelWriter extends BigExcelWriter {
+
+    public static MyExcelWriter getBigWriter() {
+        try {
+            return new MyExcelWriter();
+        } catch (NoClassDefFoundError var1) {
+            throw new DependencyException((Throwable) ObjectUtil.defaultIfNull(var1.getCause(), var1), "You need to add dependency of 'poi-ooxml' to your project, and version >= 4.1.2", new Object[0]);
+        }
+    }
+
+
+    @Override
+    public BigExcelWriter autoSizeColumnAll() {
+        final SXSSFSheet sheet = (SXSSFSheet)this.sheet;
+        sheet.trackAllColumnsForAutoSizing();
+        super.autoSizeColumnAll();
+        for (int i = 0; i <sheet.getRow(sheet.getLastRowNum()).getPhysicalNumberOfCells(); i++) {
+            // 解决自动设置列宽中文失效的问题
+            sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 3);
+        }
+        sheet.untrackAllColumnsForAutoSizing();
+        return this;
+    }
+
+}

+ 55 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/excel/annotation/ExcelField.java

@@ -0,0 +1,55 @@
+package com.yf.boot.base.api.utils.excel.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Excel导出注解定义
+ * @author bool
+ */
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcelField {
+
+	/**
+	 * 导出的excel标题
+	 * @return
+	 */
+	String title();
+
+	/**
+	 * 数据过滤,格式如:0=正常,1=禁用
+	 * @return
+	 */
+	String filter() default "";
+
+	/**
+	 * 导出字段字段排序(升序)
+	 */
+	int sort() default 0;
+
+	/**
+	 * 日期格式化
+	 * @return
+	 */
+	String pattern() default "";
+
+	/**
+	 * 数据字典类型
+	 */
+	String dictCode() default "";
+
+	/**
+	 * 查找字段
+	 * @return
+	 */
+	String dicText() default "";
+
+	/**
+	 * 查找表
+	 * @return
+	 */
+	String dictTable() default "";
+}

+ 68 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/file/MD5Util.java

@@ -0,0 +1,68 @@
+package com.yf.boot.base.api.utils.file;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+
+
+/**
+ * MD5工具类
+ * ClassName: MD5Util <br/>
+ * date: 2018年1月13日 下午6:54:53 <br/>
+ *
+ * @author Bool
+ * @version
+ */
+public class MD5Util {
+
+	
+	/**
+	 * 简单MD5
+	 * @param str
+	 * @return
+	 */
+	public static String MD5(String str) {
+
+		try {
+   		 	MessageDigest md = MessageDigest.getInstance("MD5");
+   	        byte[] array = md.digest(str.getBytes("UTF-8"));
+   	        StringBuilder sb = new StringBuilder();
+   	        for (byte item : array) {
+   	            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
+   	        }
+   	        return sb.toString();
+	   	}catch(Exception e) {
+	   		 return null;
+	   	}
+	}
+
+	/**
+	 * 获得文件的MD5值
+	 * @param file
+	 * @return
+	 */
+	public static String getFileMD5(File file) {
+		if (!file.isFile()) {
+			return null;
+		}
+		MessageDigest digest = null;
+		FileInputStream in = null;
+		byte [] buffer = new byte[1024];
+		int len;
+		try {
+			digest = MessageDigest.getInstance("MD5");
+			in = new FileInputStream(file);
+			while ((len = in.read(buffer, 0, 1024)) != -1) {
+				digest.update(buffer, 0, len);
+			}
+			in.close();
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+		BigInteger bigInt = new BigInteger(1, digest.digest());
+		return bigInt.toString(16);
+	}
+
+}

+ 169 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/file/TextFileUtils.java

@@ -0,0 +1,169 @@
+package com.yf.boot.base.api.utils.file;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * 文本处理类,用于处理读写一些文件内容,写一些日志等
+ * ClassName: TextFileUtils <br/>
+ * date: 2018年1月13日 下午6:49:04 <br/>
+ *
+ * @author Bool
+ * @version
+ */
+public class TextFileUtils {
+
+	/**
+	 * 
+	 * readText:将指定路径的文件读出里面的内容,返回一个字符串
+	 * @author Bool
+	 * @param filePath
+	 * @return
+	 */
+	public static String readText(String filePath){
+		
+		String s;
+		StringBuffer sb=new StringBuffer();
+		
+		try{
+			File f = new File(filePath);
+			if (!f.exists()) {
+				f.createNewFile();
+			}
+			BufferedReader input = new BufferedReader(new FileReader(f));
+			while ((s = input.readLine()) != null) {
+				sb.append(s).append("\n");
+			}
+			input.close();
+		}catch(Exception ex){
+			ex.printStackTrace();
+		}
+		
+		return sb.toString();
+	}
+	
+
+	/**
+	 * append:将一个字符写入一个已有的文件中,通过追加的方法追加到内容的末尾;
+	 * @author Bool
+	 * @param filePath
+	 * @param data
+	 */
+	public static synchronized void append(String filePath , String data){
+		
+		String s;
+		StringBuffer sb=new StringBuffer();
+		
+		try {
+			
+			//创建文件夹
+			String dirPath=filePath.substring(0,filePath.lastIndexOf("/")+1);
+			File dir=new File(dirPath);
+			
+			if(!dir.exists()){
+				dir.mkdirs();
+			}
+			
+			File f = new File(filePath);
+			if (!f.exists()) {
+				f.createNewFile();
+			}
+			;
+			BufferedReader input = new BufferedReader(new FileReader(f));
+					
+
+			while ((s = input.readLine()) != null) {
+				sb.append(s);
+				sb.append("\n");
+			}
+			input.close();
+			sb.append(data);
+			BufferedWriter output = new BufferedWriter(new FileWriter(f));
+			output.write(sb.toString());
+			output.close();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+
+
+	/**
+	 * 将一段文字写入指定的文件中
+	 * @param filePath
+	 * @param data
+	 */
+	public static synchronized void write(String filePath , String data){
+
+		try {
+
+			//创建文件夹
+			String dirPath = filePath.substring(0,filePath.lastIndexOf("/")+1);
+			File dir = new File(dirPath);
+
+			if(!dir.exists()){
+				dir.mkdirs();
+			}
+
+			File f = new File(filePath);
+			if (!f.exists()) {
+				f.createNewFile();
+			}
+
+			BufferedWriter output = new BufferedWriter(new FileWriter(f));
+			output.write(data);
+			output.flush();
+			output.close();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+
+	/**
+	 * 读入TXT文件
+	 */
+	public static String readFile(InputStream is) {
+
+		StringBuffer sb = new StringBuffer();
+		try (InputStreamReader reader = new InputStreamReader(is);
+			 BufferedReader br = new BufferedReader(reader)
+		) {
+			String line;
+			//网友推荐更加简洁的写法
+			while ((line = br.readLine()) != null) {
+				// 一次读入一行数据
+				sb.append(line + "\n");
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return sb.toString();
+	}
+
+
+	/**
+	 * 写入TXT文件
+	 */
+	public static void writeFile(String text, String path) {
+		try {
+			File writeName = new File(path);
+			writeName.createNewFile(); // 创建新文件,有同名的文件的话直接覆盖
+			try (FileWriter writer = new FileWriter(writeName);
+				 BufferedWriter out = new BufferedWriter(writer)
+			) {
+				out.write(text);
+				out.flush();
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+}

+ 118 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/file/ZipUtils.java

@@ -0,0 +1,118 @@
+package com.yf.boot.base.api.utils.file;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.zip.CRC32;
+import java.util.zip.CheckedOutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * 压缩文件工具类
+ * @author Dav
+ */
+public class ZipUtils {
+
+    /**
+     * 缓冲流长度
+     */
+    private static final int BUFFER_SIZE = 1024;
+
+
+    /**
+     * 压缩文件
+     * @param srcFilePath 输入文件或文件夹
+     * @param destFilePath 目标文件
+     */
+    public static void compress(String srcFilePath, String destFilePath) {
+        File src = new File(srcFilePath);
+        if (!src.exists()) {
+            throw new RuntimeException(srcFilePath + "不存在");
+        }
+        File zipFile = new File(destFilePath);
+        try {
+            FileOutputStream fos = new FileOutputStream(zipFile);
+            CheckedOutputStream cos = new CheckedOutputStream(fos, new CRC32());
+            ZipOutputStream zos = new ZipOutputStream(cos);
+            String baseDir = "";
+            compressByType(src, zos, baseDir);
+            zos.close();
+            fos.close();
+            cos.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 根据类型压缩文件
+     * @param src
+     * @param zos
+     * @param baseDir
+     */
+    private static void compressByType(File src, ZipOutputStream zos, String baseDir) {
+        if (!src.exists()) {
+            return;
+        }
+        System.out.println("压缩" + baseDir + src.getName());
+        if (src.isFile()) {
+            compressFile(src, zos, baseDir);
+        } else if (src.isDirectory()) {
+            compressDir(src, zos, baseDir);
+        }
+    }
+
+
+    /**
+     * 压缩文件
+     * @param file
+     * @param zos
+     * @param baseDir
+     */
+    private static void compressFile(File file, ZipOutputStream zos, String baseDir) {
+        if (!file.exists()) {
+            return;
+        }
+        try {
+            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
+            ZipEntry entry = new ZipEntry(baseDir + file.getName());
+            zos.putNextEntry(entry);
+            int count;
+            byte[] buf = new byte[BUFFER_SIZE];
+            while ((count = bis.read(buf)) != -1) {
+                zos.write(buf, 0, count);
+            }
+            bis.close();
+        } catch (Exception e) {
+
+        }
+    }
+
+    /**
+     * 压缩文件夹
+     * @param dir
+     * @param zos
+     * @param baseDir
+     */
+    private static void compressDir(File dir, ZipOutputStream zos, String baseDir) {
+        if (!dir.exists()) {
+            return;
+        }
+        File[] files = dir.listFiles();
+        if (files.length == 0) {
+            try {
+                zos.putNextEntry(new ZipEntry(baseDir + dir.getName()
+                        + File.separator));
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        for (File file : files) {
+            compressByType(file, zos, baseDir + dir.getName() + File.separator);
+        }
+    }
+
+}

+ 154 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/http/HttpClientUtil.java

@@ -0,0 +1,154 @@
+package com.yf.boot.base.api.utils.http;
+
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * HTTP请求工具类
+ * @author bool 
+ * @date 2016-07-20 15:31
+ */
+public class HttpClientUtil {
+
+	/**
+	 * 常规URL的连接符号
+	 */
+	private static final String PARAM_STARTER = "?";
+	private static final String PARAM_CONCAT = "&";
+	private static final String ENCODING = "UTF-8";
+
+
+	/**
+	 * 使用POST方式提交数据并获得JSON
+	 * @param url
+	 * @param params
+	 * @return
+	 */
+	public static String postRestJson(String url, Map<String,String> params) {
+
+		CloseableHttpClient client = HttpClients.createDefault();
+		try {
+
+			HttpPost httpPost = new HttpPost(url);
+
+			// 构造参数
+			List<NameValuePair> list = new ArrayList<>();
+			for (String key : params.keySet()) {
+				BasicNameValuePair vp = new BasicNameValuePair(key, params.get(key));
+				list.add(vp);
+			}
+
+			// 转换并传入参数
+			UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(list);
+			httpPost.setEntity(formEntity);
+
+			CloseableHttpResponse response = client.execute(httpPost);
+			HttpEntity entity = response.getEntity();
+			String str = EntityUtils.toString(entity, "UTF-8");
+			// 关闭
+			response.close();
+			return str;
+
+		}catch (Exception e){
+			e.printStackTrace();
+		}
+
+		return null;
+	}
+
+
+	/**
+	 * GET方法返回JSON数据
+	 * 
+	 * @param url
+	 * @return
+	 */
+	public static String getJson(String url, Map<String, String> headers, Map<String, String> params) {
+		CloseableHttpClient client = HttpClients.createDefault();
+		try {
+			
+			String fullUrl = buildParamsUrl(url, params);
+			HttpGet httpGet = new HttpGet(fullUrl);
+
+			// 循环加入文件头
+			if (headers != null && !headers.isEmpty()) {
+				for (String key : headers.keySet()) {
+					httpGet.addHeader(key, headers.get(key));
+				}
+			}
+
+			CloseableHttpResponse response = client.execute(httpGet);
+			HttpEntity entity = response.getEntity();
+			String body = EntityUtils.toString(entity, ENCODING);
+			return body;
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+		return null;
+	}
+
+	
+	/**
+	 * 构造一个带参数的GET形式URL
+	 * @param url
+	 * @param params
+	 * @return
+	 */
+	public static String buildParamsUrl(String url, Map<String, String> params) {
+
+		// 拼接参数
+		if (params != null && !params.isEmpty()) {
+			
+			StringBuffer sb = new StringBuffer(url);
+
+			//判断URL是否已经有问题号了
+			if (url.indexOf(PARAM_STARTER) == -1) {
+				sb.append(PARAM_STARTER);
+			}
+			
+			for (String key : params.keySet()) {
+
+				if(!sb.toString().endsWith(PARAM_STARTER)) {
+					sb.append(PARAM_CONCAT);
+				}
+				
+				String value = params.get(key);
+				if (StringUtils.isBlank(value)) {
+					value = "";
+				} else {
+					// 值做一下URL转码
+					try {
+						value = URLEncoder.encode(value, ENCODING);
+					} catch (UnsupportedEncodingException e) {
+						e.printStackTrace();
+						continue;
+					}
+				}
+				sb.append(key).append("=").append(value);
+			}
+			
+			return sb.toString();
+		}
+		
+		return url;
+	}
+
+}

+ 56 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/passwd/PassHandler.java

@@ -0,0 +1,56 @@
+package com.yf.boot.base.api.utils.passwd;
+
+
+import com.yf.boot.base.api.utils.file.MD5Util;
+import org.apache.commons.lang3.RandomStringUtils;
+
+/**
+ * 通用的密码处理类,用于生成密码和校验密码
+ * ClassName: PassGenerator <br/>
+ * date: 2017年12月13日 下午7:13:03 <br/>
+ *
+ * @author Bool
+ * @version
+ */
+public class PassHandler {
+	
+	/**
+	 * checkPass:校验密码是否一致
+	 * @author Bool
+	 * @param inputPass 用户传入的密码
+	 * @param salt 数据库保存的密码随机码
+	 * @param pass 数据库保存的密码MD5
+	 * @return
+	 */
+	public static boolean checkPass(String inputPass , String salt , String pass){
+		String pwdMd5 = MD5Util.MD5(inputPass);
+		return MD5Util.MD5(pwdMd5 + salt).equals(pass);
+	}
+	
+	
+	/**
+	 * 
+	 * buildPassword:用于用户注册时产生一个密码
+	 * @author Bool
+	 * @param inputPass 输入的密码
+	 * @return PassInfo 返回一个密码对象,记得保存
+	 */
+	public static PassInfo buildPassword(String inputPass) {
+
+		//产生一个6位数的随机码
+		String salt = RandomStringUtils.randomAlphabetic(6);
+		//加密后的密码
+		String encryptPassword = MD5Util.MD5(MD5Util.MD5(inputPass)+salt);
+		//返回对象
+		return new PassInfo(salt,encryptPassword);
+	}
+
+
+	public static void main(String[] args) {
+		PassInfo pa = buildPassword("4RK#nxUp1yu981Rx");
+		System.out.println("UPDATE sys_user SET `password`='"+pa.getPassword()+"',salt='"+pa.getSalt()+"',data_flag=0 WHERE user_name='admin';");
+
+		PassInfo ps = buildPassword("RzQ#zSb*MFX89bCP");
+		System.out.println("UPDATE sys_user SET `password`='"+ps.getPassword()+"',salt='"+ps.getSalt()+"',data_flag=0 WHERE user_name='student';");
+	}
+}

+ 38 - 0
exam-06173-api/src/main/java/com/yf/boot/base/api/utils/passwd/PassInfo.java

@@ -0,0 +1,38 @@
+package com.yf.boot.base.api.utils.passwd;
+
+/**
+ * 密码实体
+ * ClassName: PassInfo <br/>
+ * date: 2018年2月13日 下午7:13:50 <br/>
+ *
+ * @author Bool
+ * @version
+ */
+public class PassInfo {
+	
+	//密码随机串码
+	private String salt;
+	
+	//MD5后的密码
+	private String password;
+
+	public PassInfo(String salt, String password) {
+		super();
+		this.salt = salt;
+		this.password = password;
+	}
+	
+	public String getSalt() {
+		return salt;
+	}
+	public void setSalt(String salt) {
+		this.salt = salt;
+	}
+	public String getPassword() {
+		return password;
+	}
+	public void setPassword(String password) {
+		this.password = password;
+	}
+}
+

+ 217 - 0
exam-06173-api/src/main/java/com/yf/boot/base/aspect/data/BaseDataAspect.java

@@ -0,0 +1,217 @@
+package com.yf.boot.base.aspect.data;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.yf.boot.base.api.annon.DataProtect;
+import com.yf.boot.base.api.api.dto.BaseIdsReqDTO;
+import com.yf.boot.base.api.exception.ServiceException;
+import com.yf.boot.base.utils.InjectUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 数据字典AOP类,处理数据字典值
+ *
+ * @author bool
+ */
+@Component
+@Slf4j
+public class BaseDataAspect {
+
+    /**
+     * 数据标识,系统全部用id,所以不需要一一定义
+     */
+    private static final String DATA_ID = "id";
+
+
+    @Autowired
+    private HttpServletRequest request;
+
+    @Autowired
+    private BaseDataService baseDataService;
+
+    /**
+     * 禁止操作接口
+     */
+    private static final List<String> FORBIDDEN_API = new ArrayList<>(Arrays.asList(
+            "/api/sys/config/upload/save",
+            "/api/sys/config/sms/save",
+            "/api/sys/config/save",
+            "/api/sys/config/face/save",
+            "/api/sys/config/live/save",
+            "/api/sys/menu/save",
+            "/api/sys/menu/delete"
+            ));
+
+
+    /**
+     * 校验接口是不是被阻止运行
+     * @param pjp
+     * @throws Throwable
+     */
+    public void checkForbidden(JoinPoint pjp) throws Throwable {
+
+        // 请求地址
+        String uri = request.getRequestURI();
+
+        if(this.isForbidden(uri)){
+            throw new ServiceException("当前为演示模式,不允许进行此操作!");
+        }
+    }
+
+
+    /**
+     * 校验被保护的数据不允许操作
+     * @param pjp
+     * @return
+     * @throws Throwable
+     */
+    public Object protect(ProceedingJoinPoint pjp) throws Throwable {
+
+
+        log.info("++++++++++检测数据是否受保护");
+
+        // 获得注解
+        DataProtect pro = ((MethodSignature)pjp.getSignature()).getMethod().getAnnotation(DataProtect.class);
+
+        // 获得注解表名
+        TableName table = (TableName)pro.clazz().getAnnotation(TableName.class);
+
+        // 表名
+        String tableName = table.value();
+
+        // 请求参数
+        Object [] args = pjp.getArgs();
+
+        // 删除保护
+        if(pro.delete()){
+            this.checkDelete(pro.currUsr(), tableName, args);
+        }
+
+        if(pro.update()){
+            this.checkUpdate(pro.currUsr(), tableName, args);
+        }
+
+        return pjp.proceed();
+    }
+
+
+    /**
+     * 保护数据不被删除
+     * @param currUser
+     * @param tableName
+     * @param args
+     */
+    private void checkDelete(boolean currUser, String tableName, Object [] args){
+
+        List<String> dataIds = new ArrayList<>();
+
+        if(currUser){
+            dataIds.add(baseDataService.currentUserId());
+        }
+
+        for (Object object : args) {
+            // 删除方法获取ID列表
+            if(object instanceof BaseIdsReqDTO){
+                BaseIdsReqDTO reqDTO = (BaseIdsReqDTO)object;
+                dataIds.addAll(reqDTO.getIds());
+            }
+        }
+
+        if(!CollectionUtils.isEmpty(dataIds)){
+            // 进行校验
+            int count = baseDataService.countSystemData(tableName, dataIds);
+            if(count > 0){
+                throw new ServiceException("存在系统数据,不允许删除!");
+            }
+        }
+    }
+
+
+    /**
+     * 保护数据不被删除
+     * @param currUser
+     * @param tableName
+     * @param args
+     */
+    private void checkUpdate(Boolean currUser, String tableName, Object [] args){
+
+        List<String> dataIds = new ArrayList<>();
+
+        if(currUser){
+            dataIds.add(baseDataService.currentUserId());
+        }
+
+        for (Object object : args) {
+            String id = this.extractId(object);
+            if(!StringUtils.isEmpty(id)){
+                dataIds.add(id);
+            }
+        }
+
+        if(!CollectionUtils.isEmpty(dataIds)){
+            // 进行校验
+            int count = baseDataService.countSystemData(tableName, dataIds);
+            if(count > 0){
+                throw new ServiceException("系统数据,不允许修改!");
+            }
+        }
+    }
+
+
+    /**
+     * 获得实体的ID
+     * @param object
+     * @return
+     */
+    private String extractId(Object object){
+
+        // 提取全部字段
+        Class clazz = object.getClass();
+        List<Field> fields = InjectUtils.extractAllFields(clazz);
+
+        for(Field field: fields){
+            String name = field.getName();
+            if(DATA_ID.equals(name)){
+                // 私有属性必须设置访问权限
+                field.setAccessible(true);
+                try {
+                    String id = (String) field.get(object);
+                    return id;
+                } catch (IllegalAccessException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * 是否被禁止访问
+     * @param uri
+     * @return
+     */
+    private boolean isForbidden(String uri){
+
+        for(String item: FORBIDDEN_API){
+            if(uri.endsWith(item)){
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+}

+ 25 - 0
exam-06173-api/src/main/java/com/yf/boot/base/aspect/data/BaseDataService.java

@@ -0,0 +1,25 @@
+package com.yf.boot.base.aspect.data;
+
+import java.util.List;
+
+/**
+ * 系统数据保护业务,检测id列表是否存在data_flag=1的系统数据
+ * @author bool
+ */
+public interface BaseDataService {
+
+
+    /**
+     * 检测操作的数据中是否包含系统数据
+     * @param tableName
+     * @param ids
+     * @return
+     */
+    int countSystemData(String tableName, List<String> ids);
+
+    /**
+     * 获取当前用户ID
+     * @return
+     */
+    String currentUserId();
+}

+ 105 - 0
exam-06173-api/src/main/java/com/yf/boot/base/aspect/log/BaseLogAspect.java

@@ -0,0 +1,105 @@
+package com.yf.boot.base.aspect.log;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import com.yf.boot.base.api.annon.LogInject;
+import com.yf.boot.base.api.api.ApiRest;
+import com.yf.boot.base.api.utils.IpUtils;
+import org.apache.commons.lang3.StringUtils;
+import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO;
+import com.yf.exam.modules.user.UserUtils;
+import lombok.extern.log4j.Log4j2;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+
+/**
+ * 日志核心过滤器
+ * @author bool
+ */
+@Log4j2
+@Component
+public class BaseLogAspect {
+
+    @Autowired
+    private HttpServletRequest request;
+
+    @Autowired
+    private BaseLogService baseLogService;
+
+    /**
+     * 录制日志信息
+     * @param jp
+     * @return
+     * @throws Throwable
+     */
+    public Object record(ProceedingJoinPoint jp) throws Throwable {
+
+        // 请求地址
+        String uri = request.getRequestURI();
+
+        log.info("++++++++++注入日志信息:" + request.getRequestURI());
+
+        // IP地址
+        String ip = IpUtils.extractClientIp(request);
+
+        Method method = ((MethodSignature) jp.getSignature()).getMethod();
+
+        //接口参数
+        Object[] params = jp.getArgs();
+
+        //解析注入信息
+        LogInject ann = method.getAnnotation(LogInject.class);
+
+        // 执行返回
+        boolean success = true;
+        String log = "";
+        Object objet = null;
+        Exception e = null;
+        try{
+            objet = jp.proceed();
+        }catch (Exception e1){
+            e = e1;
+            success = false;
+            e.printStackTrace();
+            log = e.getMessage();
+        }
+
+
+        // 从token中获得用户ID
+        String userId = UserUtils.getUserId(false);
+
+        // 登录响应参数
+        if(this.isLogin(ann.logType()) && StringUtils.isBlank(userId) && success){
+            ApiRest<SysUserLoginDTO> rest =  JSON.parseObject(JSON.toJSONString(objet), new TypeReference<ApiRest<SysUserLoginDTO>>(){});
+            userId = rest.getData().getId();
+        }
+
+        // 保存日志,一定使用异步方法,避免阻塞主线程
+        baseLogService.saveLog(ann.logType(), uri, ann.title(), ip, userId, JSON.toJSONString(params), success, log);
+
+        // 存在异常,抛出
+        if(!success){
+            throw e;
+        }
+
+        return objet;
+    }
+
+    /**
+     * 是否登录接口
+     * @param logType
+     * @return
+     */
+    private boolean isLogin(String logType){
+        if(!StringUtils.isBlank(logType) && "login".equals(logType)){
+            return true;
+        }
+        return false;
+    }
+
+}

+ 29 - 0
exam-06173-api/src/main/java/com/yf/boot/base/aspect/log/BaseLogService.java

@@ -0,0 +1,29 @@
+package com.yf.boot.base.aspect.log;
+
+/**
+ * 日志数据过滤实现
+ * @author bool
+ */
+public interface BaseLogService {
+
+
+    /**
+     * 保存日志方法
+     * @param logType
+     * @param uri
+     * @param title
+     * @param ip
+     * @param userId
+     * @param jsonData
+     * @param success
+     * @param log
+     */
+    void saveLog(String logType, String uri, String title, String ip, String userId, String jsonData, Boolean success, String log);
+
+    /**
+     * 从Token中获取用户ID
+     * @param token
+     * @return
+     */
+    String getUserId(String token);
+}

+ 56 - 0
exam-06173-api/src/main/java/com/yf/boot/base/aspect/log/enums/LogType.java

@@ -0,0 +1,56 @@
+package com.yf.boot.base.aspect.log.enums;
+
+/**
+ * 日志类型
+ * 请与数据字典保持一致
+ * @author bool
+ */
+public interface LogType {
+
+    /**
+     * 登录注册
+     */
+    String LOGIN = "login";
+
+    /**
+     * 试题
+     */
+    String QU = "qu";
+
+    /**
+     * 课程
+     */
+    String COURSE = "course";
+
+    /**
+     * 直播
+     */
+    String LIVE = "live";
+
+    /**
+     * 直播
+     */
+    String EXAM = "exam";
+
+    /**
+     * 知识竞赛
+     */
+    String BATTLE = "battle";
+
+    /**
+     * 证书
+     */
+    String CERT = "cert";
+
+    /**
+     * 试卷
+     */
+    String TMPL = "tmpl";
+
+    /**
+     * 系统
+     */
+    String SYSTEM = "system";
+
+
+}

+ 25 - 0
exam-06173-api/src/main/java/com/yf/boot/base/enums/DataScope.java

@@ -0,0 +1,25 @@
+package com.yf.boot.base.enums;
+
+/**
+ * 数据权限枚举
+ * @author bool
+ */
+public interface DataScope {
+
+    /**
+     * 本人数据
+     */
+    Integer SCOPE_SELF = 1;
+    /**
+     * 本部门数据
+     */
+    Integer SCOPE_DEPT = 2;
+    /**
+     * 本部门及以下数据
+     */
+    Integer SCOPE_DEPT_DOWN = 3;
+    /**
+     * 全部数据
+     */
+    Integer SCOPE_ALL = 4;
+}

+ 17 - 0
exam-06173-api/src/main/java/com/yf/boot/base/enums/PlatformType.java

@@ -0,0 +1,17 @@
+package com.yf.boot.base.enums;
+
+/**
+ * 程序平台类型、如小程序、网页版本、H5版
+ * @author bool
+ */
+public interface PlatformType {
+
+    /**
+     * PC网页版本
+     */
+    String PC = "pc";
+    /**
+     * PDF文件
+     */
+    String H5 = "h5";
+}

+ 209 - 0
exam-06173-api/src/main/java/com/yf/boot/base/kits/db/TableUtils.java

@@ -0,0 +1,209 @@
+package com.yf.boot.base.kits.db;
+
+import com.yf.boot.base.kits.db.entity.DbColumn;
+import com.yf.boot.base.kits.db.entity.DbTable;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.util.CollectionUtils;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 批量为数据库中的表增加公共字段。
+ * 注意:
+ * 方法比较暴力,会先删除原来数据库的字段,再增加字段,会丢失原来的数据。
+ * 此功能不适合线上数据库操作,仅用于数据库设计阶段,注意!
+ *
+ * @author Van
+ * @date 2018/12/20 17:13
+ */
+@Log4j2
+public class TableUtils {
+
+    /**
+     * 数据库连接,不解释
+     */
+    private static final String SQL_URL = "jdbc:mysql://localhost:3306/yf_exam_20?characterEncoding=UTF-8" +
+            "&useUnicode" +
+            "=true&useSSL=false&tinyInt1isBit=false";
+    private static final String SQL_USER = "root";
+    private static final String SQL_PASSWORD = "root";
+
+    /**
+     * 先DROP已有的字段
+     */
+    private static final List<String> FIX_COLUMNS = Arrays.asList(
+            "create_time",
+            "update_time",
+            "create_by",
+            "update_by",
+            "data_flag");
+
+    /**
+     * 增加公共列的方法
+     */
+    private static final String ALTER_SQL = "ALTER TABLE {table} " +
+            "ADD COLUMN `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',\n" +
+            "ADD COLUMN `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '更新时间' AFTER `create_time`,\n" +
+            "ADD COLUMN `create_by` varchar(255) NOT NULL DEFAULT '' COMMENT '创建人' AFTER `update_time`,\n" +
+            "ADD COLUMN `update_by` varchar(255) NOT NULL DEFAULT '' COMMENT '修改人' AFTER `create_by`,\n" +
+            "ADD COLUMN `data_flag` int(11) NOT NULL DEFAULT 0 COMMENT '数据标识' AFTER `update_by`;";
+
+    /**
+     * 删除列的方法
+     */
+    private static final String DROP_SQL = "ALTER TABLE {table} ";
+
+
+    /**
+     * 数据库连接
+     */
+    private static Connection conn;
+
+
+    /**
+     * 操作入口
+     *
+     * @param args
+     * @throws Exception
+     */
+    public static void main(String[] args) throws Exception {
+
+        // 要处理的表前缀
+        String prefix = "sys_role_menu";
+
+        //获取数据库连接
+        conn = getMySQLConnection();
+
+        // 获取数据库下的所有表名称,包含了需要处理的字段列表
+        List<DbTable> dbTables = getAllTableName(prefix);
+
+        // 获得表中所有字段信息
+        alterColumns(dbTables);
+
+        if (conn != null) {
+            conn.close();
+        }
+    }
+
+
+    /**
+     * 获取数据库连接
+     *
+     * @return
+     * @throws Exception
+     */
+    private static Connection getMySQLConnection() throws Exception {
+        Class.forName("com.mysql.cj.jdbc.Driver");
+        Connection conn = DriverManager.getConnection(SQL_URL, SQL_USER, SQL_PASSWORD);
+        return conn;
+    }
+
+    /**
+     * 获取数据库的所有表信息
+     *
+     * @return
+     * @throws Exception
+     */
+    private static List<DbTable> getAllTableName(String prefix) throws Exception {
+        List<DbTable> dbTables = new ArrayList<>();
+        Statement stmt = conn.createStatement();
+        ResultSet rs = stmt.executeQuery("SHOW TABLES");
+        while (rs.next()) {
+            String tableName = rs.getString(1);
+            if(tableName.startsWith(prefix)) {
+                DbTable dbTable = new DbTable(tableName);
+                dbTables.add(dbTable);
+            }
+        }
+        rs.close();
+        stmt.close();
+
+        //生成完成的数据表和字段
+        buildColumns(dbTables);
+
+        return dbTables;
+    }
+
+
+    /**
+     * 获取完整的数据库表和字段(字段仅仅是包含的)
+     *
+     * @param dbTables
+     * @throws Exception
+     */
+    private static void buildColumns(List<DbTable> dbTables) throws Exception {
+        Statement stmt = conn.createStatement();
+        for (DbTable dbTable : dbTables) {
+            List<DbColumn> dbColumns = new ArrayList<>();
+            ResultSet rs = stmt.executeQuery("show full columns from " + dbTable.getTableName());
+            if (rs != null) {
+                while (rs.next()) {
+                    String field = rs.getString("Field");
+
+                    //包含在公共列中则删除
+                    if (FIX_COLUMNS.contains(field)) {
+                        DbColumn dbColumn = new DbColumn(field, field, "", "", "", "");
+                        dbColumns.add(dbColumn);
+                    }
+                }
+            }
+            if (rs != null) {
+                rs.close();
+            }
+            dbTable.setDbColumns(dbColumns);
+        }
+        stmt.close();
+    }
+
+
+    /**
+     * 批量移除和添加公共字段
+     *
+     * @param dbTables
+     * @throws Exception
+     */
+    private static void alterColumns(List<DbTable> dbTables) throws Exception {
+
+        for (DbTable dbTable : dbTables) {
+            List<DbColumn> dbColumns = dbTable.getDbColumns();
+            if (!CollectionUtils.isEmpty(dbColumns)) {
+                StringBuffer dropSql = new StringBuffer(DROP_SQL.replace("{table}", dbTable.getTableName()));
+                for (DbColumn dbColumn : dbColumns) {
+
+                    if (dropSql.indexOf("DROP") != -1) {
+                        dropSql.append(",");
+                    }
+
+                    dropSql.append(" DROP COLUMN `" + dbColumn.getField() + "`");
+                }
+                dropSql.append(";");
+                log.info(MessageFormat.format("移除表{0}的公共字段:\n{1}", dbTable.getTableName(), dropSql.toString()));
+                executeSql(dropSql.toString());
+            }
+            String alterSql = ALTER_SQL.replace("{table}", dbTable.getTableName());
+            log.info(MessageFormat.format("新增表{0}的公共字段:\n{1}", dbTable.getTableName(), alterSql));
+            executeSql(alterSql);
+        }
+
+    }
+
+    /**
+     * 执行SQL
+     *
+     * @param sql
+     * @throws Exception
+     */
+    private static void executeSql(String sql) throws Exception {
+        Statement stmt = conn.createStatement();
+        stmt.execute(sql);
+        stmt.close();
+    }
+
+}

+ 52 - 0
exam-06173-api/src/main/java/com/yf/boot/base/kits/db/entity/DbColumn.java

@@ -0,0 +1,52 @@
+package com.yf.boot.base.kits.db.entity;
+
+import lombok.Data;
+
+/**
+ * 数据库字段
+ *
+ * @author Van
+ * @date 2018/12/20 17:11
+ */
+@Data
+public class DbColumn {
+    /**
+     * 数据库字段名称
+     */
+    private String field;
+
+    /**
+     * 服务端model属性名称
+     */
+    private String param;
+
+    /**
+     * 数据库字段类型
+     */
+    private String type;
+
+    /**
+     * 数据库字段注释
+     */
+    private String comment;
+
+    /**
+     * 是否可以为空
+     */
+    private String nullable;
+
+    /**
+     * 默认值
+     */
+    private String defaultValue;
+
+
+    public DbColumn(String field, String param, String type, String comment, String nullable, String defaultValue) {
+        this.field = field;
+        this.param = param;
+        this.type = type;
+        this.comment = comment;
+        this.nullable = nullable;
+        this.defaultValue = defaultValue;
+    }
+}

+ 35 - 0
exam-06173-api/src/main/java/com/yf/boot/base/kits/db/entity/DbTable.java

@@ -0,0 +1,35 @@
+package com.yf.boot.base.kits.db.entity;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 数据表信息
+ *
+ * @author Van
+ * @date 2018/12/20 17:11
+ */
+@Data
+public class DbTable {
+
+    /**
+     * 数据库表名
+     */
+    private String tableName;
+
+    /**
+     * 数据库表的建表语句
+     */
+    private String comment;
+
+    /**
+     * 表包含的字段
+     */
+    private List<DbColumn> dbColumns;
+
+    public DbTable(String tableName) {
+        this.tableName = tableName;
+    }
+
+}

+ 30 - 0
exam-06173-api/src/main/java/com/yf/boot/base/utils/AbcTags.java

@@ -0,0 +1,30 @@
+package com.yf.boot.base.utils;
+
+/**
+ * 根据索引获取ABC
+ * @author bool
+ */
+public class AbcTags {
+
+
+    /**
+     * 获取索引对应的ABC
+     */
+    public static String[] tags = new String[]{
+            "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U",
+            "V", "W", "X"
+            , "Y", "Z"
+    };
+
+    /**
+     * 获得ABC字符
+     * @param index
+     * @return
+     */
+    public static String get(int index){
+        if(index > tags.length){
+            return "";
+        }
+        return tags[index];
+    }
+}

+ 31 - 0
exam-06173-api/src/main/java/com/yf/boot/base/utils/CronUtils.java

@@ -0,0 +1,31 @@
+package com.yf.boot.base.utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * 时间转换quartz表达式
+ * @author bool 
+ * @date 2020/11/29 下午3:00
+ */
+public class CronUtils {
+
+    /**
+     * 格式化数据
+     */
+    private static final String DATE_FORMAT = "ss mm HH dd MM ? yyyy";
+
+    /**
+     * 准确的时间点到表达式
+     * @param date
+     * @return
+     */
+    public static String dateToCron(final Date date){
+        SimpleDateFormat fmt = new SimpleDateFormat(DATE_FORMAT);
+        String formatTimeStr = "";
+        if (date != null) {
+            formatTimeStr = fmt.format(date);
+        }
+        return formatTimeStr;
+    }
+}

+ 53 - 0
exam-06173-api/src/main/java/com/yf/boot/base/utils/FileUtils.java

@@ -0,0 +1,53 @@
+package com.yf.boot.base.utils;
+
+import java.io.File;
+
+/**
+ * @author bool
+ */
+public class FileUtils {
+
+    public static boolean checkDelete(String path){
+
+        if(path.contains("/.git/") || path.contains("/target/") || path.contains("/.idea/")){
+            return true;
+        }
+
+        return false;
+    }
+
+
+    public static void deleteDir(File file){
+
+        // 文件夹不存在
+        if(!file.exists() || !file.isDirectory()){
+            return;
+        }
+
+
+        File [] files = file.listFiles();
+        if(files == null || files.length==0){
+            return;
+        }
+
+        for(File item: files){
+            if(file.isDirectory()){
+                deleteDir(item);
+                continue;
+            }
+
+
+            if(checkDelete(item.getAbsolutePath())){
+                System.out.println("+++++++++删除文件:"+item.getAbsolutePath());
+                item.delete();
+            }
+        }
+
+        // 删除大文件夹
+        if(checkDelete(file.getAbsolutePath())){
+            System.out.println("+++++++++删除文件:"+file.getAbsolutePath());
+            file.delete();
+        }
+    }
+
+}

+ 57 - 0
exam-06173-api/src/main/java/com/yf/boot/base/utils/ImageUtils.java

@@ -0,0 +1,57 @@
+package com.yf.boot.base.utils;
+
+import javax.imageio.stream.FileImageInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Base64;
+
+public class ImageUtils {
+
+
+    //图片到byte数组
+    public static byte [] image2byte(String path){
+        byte[] data = null;
+        FileImageInputStream input = null;
+        try {
+            input = new FileImageInputStream(new File(path));
+            ByteArrayOutputStream output = new ByteArrayOutputStream();
+            byte[] buf = new byte[1024];
+            int numBytesRead = 0;
+            while ((numBytesRead = input.read(buf)) != -1) {
+                output.write(buf, 0, numBytesRead);
+            }
+            data = output.toByteArray();
+            output.close();
+            input.close();
+        }
+        catch (FileNotFoundException ex1) {
+            ex1.printStackTrace();
+        }
+        catch (IOException ex1) {
+            ex1.printStackTrace();
+        }
+        return data;
+    }
+
+
+    /**
+     * 图片转base64字符串
+     *
+     * @param imgFile 图片路径
+     * @return
+     */
+    public static String imageToBase64Str(String imgFile) {
+
+        try {
+            byte[] fileContent = org.apache.commons.io.FileUtils.readFileToByteArray(new File(imgFile));
+            return Base64.getEncoder().encodeToString(fileContent);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+}

+ 127 - 0
exam-06173-api/src/main/java/com/yf/boot/base/utils/InjectUtils.java

@@ -0,0 +1,127 @@
+package com.yf.boot.base.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.yf.boot.base.api.api.ApiError;
+import com.yf.boot.base.api.api.ApiRest;
+import lombok.extern.log4j.Log4j2;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 注入工具类
+ * @author bool
+ * @date 2019-07-17 09:32
+ */
+@Log4j2
+public class InjectUtils {
+
+
+    /**
+     * 给对象字段赋值
+     *
+     * @param object 赋值的对象
+     * @param value  值
+     * @param fields 字段
+     * @throws Exception 异常
+     */
+    public static void setValue(Object object, Object value, String... fields) throws Exception {
+
+        //设置同类的属性
+        for (String fieldName : fields) {
+
+            //获取当前
+            Field field = getField(object.getClass(), fieldName);
+            if(field == null){
+                continue;
+            }
+
+            field.setAccessible(true);
+            field.set(object, value);
+        }
+
+    }
+
+    /**
+     * 查找字段包含父类属性
+     * @param clazz
+     * @param name
+     * @return
+     */
+    public static Field getField(Class<?> clazz, String name){
+
+        //遍历全部的域
+        Field[] fields = clazz.getDeclaredFields();
+        for (Field field : fields){
+            if (name.equals(field.getName())){
+                return field;
+            }
+        }
+
+        Class<?> superclass = clazz.getSuperclass();
+        if (null == superclass){
+            return null;
+        }
+        return getField(superclass, name);
+    }
+
+
+    /**
+     * 打印结果返回
+     * @param response
+     * @throws IOException
+     */
+    public static void restError(HttpServletResponse response) {
+
+        try {
+
+            //固定错误
+            ApiRest apiRest = new ApiRest(ApiError.ERROR_10010002);
+            response.setCharacterEncoding("UTF-8");
+            response.setContentType("application/json");
+            response.getWriter().write(JSON.toJSONString(apiRest));
+            response.getWriter().close();
+
+        }catch (IOException e){
+
+        }
+    }
+
+
+    /**
+     * 获得包含父类属性的全部字段
+     * @param clazz
+     * @param allFields
+     */
+    public static void extractFields(Class clazz, List<Field> allFields){
+
+        // 获取对象属性
+        Field [] fields = clazz.getDeclaredFields();
+        for(Field field: fields){
+            allFields.add(field);
+        }
+
+        if(clazz.getSuperclass()!=null){
+            extractFields(clazz.getSuperclass(), allFields);
+        }
+    }
+
+    /**
+     * 提取全部字段,包含父类的字段在内的
+     * @param clazz
+     * @return
+     */
+    public static List<Field> extractAllFields(Class clazz){
+        // 提取全部字段
+        List<Field> fields = new ArrayList<>();
+        extractFields(clazz, fields);
+        return fields;
+    }
+
+
+
+
+}

+ 91 - 0
exam-06173-api/src/main/java/com/yf/boot/base/utils/ListUtils.java

@@ -0,0 +1,91 @@
+package com.yf.boot.base.utils;
+
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.List;
+
+/**
+ * 集合工具类
+ * @author bool
+ */
+public class ListUtils {
+
+    /**
+     * 对比两个列表的值是否一样,不限顺序
+     * @param sampleList
+     * @param inputList
+     * @return
+     */
+    public static boolean compareList(List<String> sampleList, List<String> inputList){
+
+        // 为空都返回
+        if(CollectionUtils.isEmpty(sampleList)
+                || CollectionUtils.isEmpty(inputList)){
+            return false;
+        }
+
+        // 必须数量一样
+        if(sampleList.size()!=inputList.size()){
+            return false;
+        }
+
+        // 比较列表
+        for(String item: sampleList){
+            if(!contains(inputList, item)){
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * 判断文字是否包含
+     * @param sampleList
+     * @param str
+     * @return
+     */
+    public static boolean contains(List<String> sampleList, String str){
+
+        // 空不比较
+        if(CollectionUtils.isEmpty(sampleList) || StringUtils.isEmpty(str)){
+            return false;
+        }
+
+        for(String item: sampleList){
+
+            if(str.trim().equalsIgnoreCase(item.trim())){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 列表转换String
+     * @param list
+     * @return
+     */
+    public static String listToString(List<String> list){
+
+        if(CollectionUtils.isEmpty(list)){
+            return "";
+        }
+
+        StringBuffer sb = null;
+        for(String str: list){
+            if(sb == null){
+                sb = new StringBuffer();
+            }else{
+                sb.append(",");
+            }
+
+            sb.append(str);
+        }
+
+        return sb.toString();
+    }
+
+
+}

+ 44 - 0
exam-06173-api/src/main/java/com/yf/boot/base/utils/MessageUtils.java

@@ -0,0 +1,44 @@
+package com.yf.boot.base.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.yf.boot.base.api.api.ApiRest;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 消息工具类
+ * @author bool
+ * @date 2019-07-17 09:32
+ */
+@Log4j2
+@Component
+public class MessageUtils {
+
+
+    /**
+     * 通过response写出JSON数据
+     * @param response
+     */
+    public static void writeError(HttpServletResponse response, String msg) {
+
+        try {
+
+            //固定错误
+            ApiRest apiRest = new ApiRest();
+            apiRest.setMsg(msg);
+            apiRest.setCode(1);
+            response.setCharacterEncoding("UTF-8");
+            response.setContentType("application/json");
+            response.getWriter().write(JSON.toJSONString(apiRest));
+            response.getWriter().close();
+
+        }catch (IOException e){
+
+        }
+    }
+
+
+}

+ 96 - 0
exam-06173-api/src/main/java/com/yf/boot/base/utils/ResourceUtil.java

@@ -0,0 +1,96 @@
+package com.yf.boot.base.utils;
+
+import org.apache.commons.io.FileUtils;
+import org.springframework.core.io.ClassPathResource;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+
+/**
+ * 资源工具
+ * @author bool
+ */
+public class ResourceUtil {
+
+
+    /**
+     * 数据流转换btye[]
+     * @param input
+     * @return
+     * @throws IOException
+     */
+    public static byte[] toByteArray(InputStream input) throws IOException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024*4];
+        int n = 0;
+        while (-1 != (n = input.read(buffer))) {
+            output.write(buffer, 0, n);
+        }
+        return output.toByteArray();
+    }
+
+
+    /**
+     * 从资源文件写出流
+     * @param response
+     * @param path
+     * @throws Exception
+     */
+    public static void write(HttpServletResponse response, String path) throws Exception{
+
+        // 获取文件读成流
+        InputStream fis = new ClassPathResource(path).getInputStream();
+        byte [] data = toByteArray(fis);
+
+        // 设置响应头
+        response.setHeader("Access-Control-Allow-Origin", "*");
+        response.setContentType("application/octet-stream; charset=utf-8");
+
+        // 输出流文件
+        OutputStream os = new BufferedOutputStream(response.getOutputStream());
+        os.write(data);
+        os.flush();
+        os.close();
+    }
+
+    /**
+     * 获取模板临时目录
+     * @param create
+     * @return
+     */
+    public static String tempDir(boolean create){
+        String path = System.getProperty("java.io.tmpdir");
+        if(create){
+            File file = new File(path);
+            if(!file.exists()){
+                file.mkdirs();
+            }
+        }
+        return path;
+    }
+
+    /**
+     * 获取文件完整地址
+     * @param path
+     * @return
+     */
+    public static String fullPath(String path) throws IOException {
+
+        // 临时目录
+        String filePath = tempDir(true) + path;
+        File file = new File(filePath);
+        if(file.exists()){
+            return filePath;
+        }
+
+        // 获取文件读成流
+        InputStream is = new ClassPathResource(path).getInputStream();
+        FileUtils.copyInputStreamToFile(is, file);
+        return filePath;
+    }
+
+    public static void main(String[] args) throws IOException {
+
+        System.out.println(fullPath("tmpl/learn-cert.docx"));
+    }
+}

+ 98 - 0
exam-06173-api/src/main/java/com/yf/exam/ExamApplication.java

@@ -0,0 +1,98 @@
+package com.yf.exam;
+
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.alibaba.fastjson.support.config.FastJsonConfig;
+import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
+import com.yf.exam.aspect.dict.DataDictFilter;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.core.env.Environment;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 云帆在线考试系统
+ * @author bool
+ * @email 18365918@qq.com
+ * @date 2020-03-04 19:41
+ */
+@Log4j2
+@SpringBootApplication
+@EnableCaching
+@ComponentScan(basePackages = {"com.yf"})
+public class ExamApplication implements WebMvcConfigurer {
+
+	public static void main(String[] args) throws UnknownHostException {
+
+		ConfigurableApplicationContext application = SpringApplication.run(ExamApplication.class, args);
+		Environment env = application.getEnvironment();
+		String ip = InetAddress.getLocalHost().getHostAddress();
+		String port = env.getProperty("server.port");
+		String path = env.getProperty("server.servlet.context-path");
+
+		// 未配置默认空白
+		if(path == null){
+			path = "";
+		}
+
+		log.info("\n----------------------------------------------------------\n\t" +
+				"云帆考试系统启动成功,访问路径如下:\n\t" +
+				"本地路径: \t\thttp://localhost:" + port + path + "/\n\t" +
+				"网络地址: \thttp://" + ip + ":" + port + path + "/\n\t" +
+				"API文档: \t\thttp://" + ip + ":" + port + path + "/doc.html\n" +
+				"----------------------------------------------------------");
+	}
+
+
+	@Override
+	public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
+		//保留原有converter,把新增fastConverter插入集合头,保证优先级
+		converters.add(0, fastConverter());
+	}
+
+	/**
+	 * FastJson消息转换器
+	 *
+	 * @return
+	 */
+	public static HttpMessageConverter fastConverter() {
+		// 定义一个convert转换消息的对象
+		FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
+		// 添加FastJson的配置信息
+		FastJsonConfig fastJsonConfig = new FastJsonConfig();
+		// 数据脱敏及字典翻译
+		fastJsonConfig.setSerializeFilters(new DataDictFilter());
+		// 默认转换器
+		fastJsonConfig.setSerializerFeatures(
+				SerializerFeature.PrettyFormat,
+				SerializerFeature.WriteNullNumberAsZero,
+				SerializerFeature.MapSortField,
+				SerializerFeature.WriteNullStringAsEmpty,
+				SerializerFeature.DisableCircularReferenceDetect,
+				SerializerFeature.WriteDateUseDateFormat,
+				SerializerFeature.WriteNullListAsEmpty);
+		fastJsonConfig.setCharset(Charset.forName("UTF-8"));
+
+
+		// 处理中文乱码问题
+		List<MediaType> fastMediaTypes = new ArrayList<>();
+		fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
+		fastConverter.setSupportedMediaTypes(fastMediaTypes);
+		// 在convert中添加配置信息
+		fastConverter.setFastJsonConfig(fastJsonConfig);
+
+		return fastConverter;
+	}
+
+}

+ 30 - 0
exam-06173-api/src/main/java/com/yf/exam/ability/Constant.java

@@ -0,0 +1,30 @@
+package com.yf.exam.ability;
+
+
+/**
+ * 通用常量
+ * @author bool
+ */
+public class Constant {
+
+    /**
+     * 用户名前缀
+     */
+    public static final String USER_NAME_KEY = "yf:exam:name:";
+
+    /**
+     * 会话
+     */
+    public static final String TOKEN = "token";
+
+
+    /**
+     * 人脸识别
+     */
+    public static final String FACE_TOKEN = "yf:face:";
+
+    /**
+     * 文件上传路径
+     */
+    public static final String FILE_PREFIX = "/upload/file/";
+}

+ 72 - 0
exam-06173-api/src/main/java/com/yf/exam/ability/captcha/controller/CaptchaController.java

@@ -0,0 +1,72 @@
+package com.yf.exam.ability.captcha.controller;
+
+import com.wf.captcha.SpecCaptcha;
+import com.yf.boot.base.api.api.ApiRest;
+import com.yf.boot.base.api.api.controller.BaseController;
+import com.yf.exam.ability.captcha.dto.request.CheckCaptchaReqDTO;
+import com.yf.exam.ability.captcha.service.CaptchaService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * <p>
+ * 图形验证码生成
+ * </p>
+ *
+ * @author 聪明笨狗
+ *
+ * @since 2019-04-16 10:14
+ */
+@Api(tags = {"验证码生成类"})
+@RestController
+@RequestMapping("/api/common/captcha")
+public class CaptchaController extends BaseController {
+
+    @Autowired
+    private CaptchaService captchaService;
+
+
+    @RequestMapping(value="/gen", method = RequestMethod.GET)
+    @ApiOperation(value = "生成图形验证码")
+    public void captcha(HttpServletResponse response, @RequestParam("key") String key) throws Exception {
+
+        // 设置请求头为输出图片类型
+        response.setContentType("image/gif");
+        response.setHeader("Pragma", "No-cache");
+        response.setHeader("Cache-Control", "no-cache");
+        response.setDateHeader("Expires", 0);
+
+        // 算术类型
+        SpecCaptcha captcha = new SpecCaptcha(130, 48);
+        // 几位数运算,默认是两位
+        captcha.setLen(4);
+
+        // 输出图片流
+        ServletOutputStream os = null;
+        try {
+            os = response.getOutputStream();
+            captcha.out(os);
+        }finally {
+            os.close();
+        }
+
+        // 存入REDIS
+        captchaService.saveCaptcha(key, captcha.text().toLowerCase());
+
+    }
+
+
+    @PostMapping("/check")
+    @ApiOperation(value = "校验验证码")
+    public ApiRest<Boolean> check(@RequestBody CheckCaptchaReqDTO reqDTO) throws Exception {
+        boolean result = captchaService.checkCaptcha(reqDTO.getCaptchaKey(), reqDTO.getCaptchaValue());
+        return super.success(result);
+    }
+
+}

+ 29 - 0
exam-06173-api/src/main/java/com/yf/exam/ability/captcha/dto/request/CheckCaptchaReqDTO.java

@@ -0,0 +1,29 @@
+package com.yf.exam.ability.captcha.dto.request;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 图形验证码校验请求类
+ * @author bool
+ * @date 2020-02-21 10:18
+ */
+@Data
+@ApiModel(value="图形验证码校验请求类", description="图形验证码校验请求类")
+public class CheckCaptchaReqDTO implements Serializable {
+
+    /**
+     * 验证码键
+     */
+    @ApiModelProperty(value = "前端产生的验证码键")
+    private String captchaKey;
+
+    /**
+     * 用户输入的验证码
+     */
+    @ApiModelProperty(value = "用户输入的验证码")
+    private String captchaValue;
+}

+ 24 - 0
exam-06173-api/src/main/java/com/yf/exam/ability/captcha/service/CaptchaService.java

@@ -0,0 +1,24 @@
+package com.yf.exam.ability.captcha.service;
+
+/**
+ * 验证码业务类
+ * @author bool
+ * @date 2020-02-17 09:43
+ */
+public interface CaptchaService {
+
+    /**
+     * 保存验证码信息到Redis
+     * @param key
+     * @param value
+     */
+    void saveCaptcha(String key, String value);
+
+    /**
+     * 校验验证码内容是否正确
+     * @param key
+     * @param input
+     * @return
+     */
+    boolean checkCaptcha(String key, String input);
+}

+ 58 - 0
exam-06173-api/src/main/java/com/yf/exam/ability/captcha/service/impl/CaptchaServiceImpl.java

@@ -0,0 +1,58 @@
+package com.yf.exam.ability.captcha.service.impl;
+
+
+import com.yf.exam.ability.captcha.service.CaptchaService;
+import com.yf.exam.ability.redis.service.RedisService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 验证码业务类
+ * @author bool
+ * @date 2020-02-21 10:05
+ */
+@Service
+public class CaptchaServiceImpl implements CaptchaService {
+
+    @Autowired
+    private RedisService redisService;
+
+    /**
+     * 验证码缓存前缀
+     */
+    private static final String CAPTCHA_PREFIX = "sys:captcha:";
+
+    @Override
+    public void saveCaptcha(String key, String value) {
+        redisService.set(appendKey(key), value, 300L);
+    }
+
+    @Override
+    public boolean checkCaptcha(String key, String input) {
+
+        // 完整KEY
+        String fullKey = appendKey(key);
+
+        String value = redisService.getString(fullKey);
+
+        // 校验
+        boolean result = StringUtils.isNotBlank(value) && value.equalsIgnoreCase(input);
+
+        // 验证正确就清除
+        if(result) {
+            redisService.del(fullKey);
+        }
+
+        return result;
+    }
+
+    /**
+     * 组合KEY
+     * @param key
+     * @return
+     */
+    private String appendKey(String key){
+        return new StringBuffer(CAPTCHA_PREFIX).append(key).toString();
+    }
+}

+ 22 - 0
exam-06173-api/src/main/java/com/yf/exam/ability/desensitize/annon/Desensitized.java

@@ -0,0 +1,22 @@
+package com.yf.exam.ability.desensitize.annon;
+
+import com.yf.exam.ability.desensitize.enums.DesensitizeType;
+
+import java.lang.annotation.*;
+
+
+/**
+ * 脱敏注解类
+ */
+@Target({ElementType.FIELD, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface Desensitized {
+
+    // 加密类型
+    DesensitizeType type() default DesensitizeType.STRING;
+
+    // 类型
+    String [] props() default {};
+}

+ 10 - 0
exam-06173-api/src/main/java/com/yf/exam/ability/desensitize/enums/DesensitizeType.java

@@ -0,0 +1,10 @@
+package com.yf.exam.ability.desensitize.enums;
+
+/**
+ * 脱敏字段类型
+ */
+public enum DesensitizeType {
+
+    // 字符串和JSON字符串处理不一样
+    STRING, JSON_STRING
+}

+ 56 - 0
exam-06173-api/src/main/java/com/yf/exam/ability/desensitize/filter/DesensitizeFilter.java

@@ -0,0 +1,56 @@
+package com.yf.exam.ability.desensitize.filter;
+
+import com.alibaba.fastjson.serializer.ValueFilter;
+import com.yf.boot.base.utils.InjectUtils;
+import com.yf.exam.ability.desensitize.annon.Desensitized;
+import com.yf.exam.ability.desensitize.enums.DesensitizeType;
+import com.yf.exam.ability.desensitize.utils.DesensitizeUtils;
+import lombok.Data;
+import lombok.extern.log4j.Log4j2;
+
+import java.lang.reflect.Field;
+
+/**
+ * 数据脱敏工作
+ *
+ * @author van
+ */
+@Data
+@Log4j2
+public class DesensitizeFilter implements ValueFilter {
+
+
+    @Override
+    public Object process(Object object, String name, Object value) {
+
+        // 只过滤String类型
+        if (null == value || !(value instanceof String)) {
+            return value;
+        }
+        try {
+            Field field = InjectUtils.getField(object.getClass(), name);
+            if (String.class != field.getType()) {
+                return value;
+            }
+
+            Desensitized ann = field.getAnnotation(Desensitized.class);
+            if (ann == null) {
+                return value;
+            }
+
+            // 字符类型
+            String str = String.valueOf(value);
+
+            // JSON字符串脱敏
+            if(DesensitizeType.JSON_STRING.equals(ann.type())){
+                return DesensitizeUtils.encryptJson(str, ann.props());
+            }else{
+                return DesensitizeUtils.encrypt(str);
+            }
+
+        } catch (Exception e) {
+            // log.error("当前数据类型为{},值为{}", object.getClass(), value);
+            return value;
+        }
+    }
+}

+ 0 - 0
exam-06173-api/src/main/java/com/yf/exam/ability/desensitize/utils/DesensitizeUtils.java


Vissa filer visades inte eftersom för många filer har ändrats