yangdamao vor 1 Woche
Commit
203604ef80
100 geänderte Dateien mit 12434 neuen und 0 gelöschten Zeilen
  1. 33 0
      .gitignore
  2. 96 0
      logs/sys-console.log
  3. 0 0
      logs/sys-error.log
  4. 80 0
      logs/sys-info.log
  5. 0 0
      logs/sys-user.log
  6. 358 0
      pom.xml
  7. 19 0
      src/main/java/com/zhongzheng/SaaaMarketingApplication.java
  8. 18 0
      src/main/java/com/zhongzheng/SassMarketingServletInitializer.java
  9. 25 0
      src/main/java/com/zhongzheng/bo/OssCallbackParam.java
  10. 35 0
      src/main/java/com/zhongzheng/bo/OssRequest.java
  11. 43 0
      src/main/java/com/zhongzheng/bo/SysDictDataBo.java
  12. 41 0
      src/main/java/com/zhongzheng/bo/SysDictTypeBo.java
  13. 67 0
      src/main/java/com/zhongzheng/bo/SysUserEditBo.java
  14. 23 0
      src/main/java/com/zhongzheng/bo/UserMarketingLoginBo.java
  15. 165 0
      src/main/java/com/zhongzheng/common/annotation/Excel.java
  16. 18 0
      src/main/java/com/zhongzheng/common/annotation/Excels.java
  17. 18 0
      src/main/java/com/zhongzheng/common/annotation/RepeatSubmit.java
  18. 24 0
      src/main/java/com/zhongzheng/common/config/AdminServerConfig.java
  19. 58 0
      src/main/java/com/zhongzheng/common/config/CaptchaConfig.java
  20. 61 0
      src/main/java/com/zhongzheng/common/config/FilterConfig.java
  21. 111 0
      src/main/java/com/zhongzheng/common/config/MybatisPlusConfig.java
  22. 20 0
      src/main/java/com/zhongzheng/common/config/OssConfig.java
  23. 46 0
      src/main/java/com/zhongzheng/common/config/RedisConfig.java
  24. 66 0
      src/main/java/com/zhongzheng/common/config/ResourcesConfig.java
  25. 76 0
      src/main/java/com/zhongzheng/common/config/RuoYiConfig.java
  26. 157 0
      src/main/java/com/zhongzheng/common/config/SecurityConfig.java
  27. 32 0
      src/main/java/com/zhongzheng/common/config/ServerConfig.java
  28. 148 0
      src/main/java/com/zhongzheng/common/config/SwaggerConfig.java
  29. 71 0
      src/main/java/com/zhongzheng/common/config/ThreadPoolConfig.java
  30. 90 0
      src/main/java/com/zhongzheng/common/croe/AsyncFactory.java
  31. 57 0
      src/main/java/com/zhongzheng/common/croe/AsyncManager.java
  32. 105 0
      src/main/java/com/zhongzheng/common/croe/BaseController.java
  33. 50 0
      src/main/java/com/zhongzheng/common/croe/CreateAndUpdateMetaObjectHandler.java
  34. 95 0
      src/main/java/com/zhongzheng/common/croe/CustomTenantLineHandler.java
  35. 72 0
      src/main/java/com/zhongzheng/common/croe/FastJson2JsonRedisSerializer.java
  36. 56 0
      src/main/java/com/zhongzheng/common/croe/HttpHelper.java
  37. 49 0
      src/main/java/com/zhongzheng/common/croe/LoginBody.java
  38. 148 0
      src/main/java/com/zhongzheng/common/croe/LoginUser.java
  39. 344 0
      src/main/java/com/zhongzheng/common/croe/RedisCache.java
  40. 12 0
      src/main/java/com/zhongzheng/common/croe/RedisLockEntity.java
  41. 30 0
      src/main/java/com/zhongzheng/common/croe/UserStatus.java
  42. 97 0
      src/main/java/com/zhongzheng/common/filter/BaseException.java
  43. 16 0
      src/main/java/com/zhongzheng/common/filter/CaptchaException.java
  44. 16 0
      src/main/java/com/zhongzheng/common/filter/CaptchaExpireException.java
  45. 167 0
      src/main/java/com/zhongzheng/common/filter/Constants.java
  46. 43 0
      src/main/java/com/zhongzheng/common/filter/CustomException.java
  47. 17 0
      src/main/java/com/zhongzheng/common/filter/FileException.java
  48. 16 0
      src/main/java/com/zhongzheng/common/filter/FileNameLengthLimitExceededException.java
  49. 16 0
      src/main/java/com/zhongzheng/common/filter/FileSizeLimitExceededException.java
  50. 72 0
      src/main/java/com/zhongzheng/common/filter/InvalidExtensionException.java
  51. 50 0
      src/main/java/com/zhongzheng/common/filter/JwtAuthenticationTokenFilter.java
  52. 48 0
      src/main/java/com/zhongzheng/common/filter/RepeatableFilter.java
  53. 78 0
      src/main/java/com/zhongzheng/common/filter/RepeatedlyRequestWrapper.java
  54. 63 0
      src/main/java/com/zhongzheng/common/filter/UserConstants.java
  55. 16 0
      src/main/java/com/zhongzheng/common/filter/UserException.java
  56. 17 0
      src/main/java/com/zhongzheng/common/filter/UserPasswordNotMatchException.java
  57. 93 0
      src/main/java/com/zhongzheng/common/filter/XssFilter.java
  58. 105 0
      src/main/java/com/zhongzheng/common/filter/XssHttpServletRequestWrapper.java
  59. 56 0
      src/main/java/com/zhongzheng/common/interceptor/RepeatSubmitInterceptor.java
  60. 126 0
      src/main/java/com/zhongzheng/common/interceptor/impl/SameUrlDataInterceptor.java
  61. 192 0
      src/main/java/com/zhongzheng/common/model/AjaxResult.java
  62. 34 0
      src/main/java/com/zhongzheng/common/model/AuthenticationEntryPointImpl.java
  63. 49 0
      src/main/java/com/zhongzheng/common/model/BaseEntity.java
  64. 52 0
      src/main/java/com/zhongzheng/common/model/LogoutSuccessHandlerImpl.java
  65. 21 0
      src/main/java/com/zhongzheng/common/model/ResultBean.java
  66. 150 0
      src/main/java/com/zhongzheng/common/model/SysLoginService.java
  67. 80 0
      src/main/java/com/zhongzheng/common/model/SysLogininfor.java
  68. 66 0
      src/main/java/com/zhongzheng/common/model/SysPermissionService.java
  69. 47 0
      src/main/java/com/zhongzheng/common/model/TableDataInfo.java
  70. 272 0
      src/main/java/com/zhongzheng/common/model/TokenService.java
  71. 64 0
      src/main/java/com/zhongzheng/common/model/UserDetailsServiceImpl.java
  72. 11 0
      src/main/java/com/zhongzheng/common/schedule/IScheduleService.java
  73. 17 0
      src/main/java/com/zhongzheng/common/schedule/impl/ScheduleServiceImpl.java
  74. 159 0
      src/main/java/com/zhongzheng/common/util/AES.java
  75. 55 0
      src/main/java/com/zhongzheng/common/util/AddressUtils.java
  76. 752 0
      src/main/java/com/zhongzheng/common/util/DateUtils.java
  77. 216 0
      src/main/java/com/zhongzheng/common/util/DictUtils.java
  78. 195 0
      src/main/java/com/zhongzheng/common/util/DoubleUtils.java
  79. 174 0
      src/main/java/com/zhongzheng/common/util/EasyPoiUtil.java
  80. 1473 0
      src/main/java/com/zhongzheng/common/util/ExcelUtil.java
  81. 77 0
      src/main/java/com/zhongzheng/common/util/FileTypeUtils.java
  82. 389 0
      src/main/java/com/zhongzheng/common/util/FileUploadUtils.java
  83. 569 0
      src/main/java/com/zhongzheng/common/util/FileUtils.java
  84. 728 0
      src/main/java/com/zhongzheng/common/util/HttpUtils.java
  85. 131 0
      src/main/java/com/zhongzheng/common/util/ImageUtils.java
  86. 196 0
      src/main/java/com/zhongzheng/common/util/IpUtils.java
  87. 46 0
      src/main/java/com/zhongzheng/common/util/JavaMailUtils.java
  88. 25 0
      src/main/java/com/zhongzheng/common/util/MessageUtils.java
  89. 55 0
      src/main/java/com/zhongzheng/common/util/MimeTypeUtils.java
  90. 405 0
      src/main/java/com/zhongzheng/common/util/ReflectUtils.java
  91. 110 0
      src/main/java/com/zhongzheng/common/util/SecurityUtils.java
  92. 159 0
      src/main/java/com/zhongzheng/common/util/ServletUtils.java
  93. 52 0
      src/main/java/com/zhongzheng/common/util/SmsUtils.java
  94. 128 0
      src/main/java/com/zhongzheng/common/util/SnowflakeIdUtils.java
  95. 160 0
      src/main/java/com/zhongzheng/common/util/SpringUtils.java
  96. 37 0
      src/main/java/com/zhongzheng/common/util/SqlUtil.java
  97. 73 0
      src/main/java/com/zhongzheng/common/util/TelPhoneUtils.java
  98. 96 0
      src/main/java/com/zhongzheng/common/util/Threads.java
  99. 653 0
      src/main/java/com/zhongzheng/common/util/ToolsUtils.java
  100. 137 0
      src/main/java/com/zhongzheng/controller/CaptchaController.java

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 96 - 0
logs/sys-console.log

@@ -0,0 +1,96 @@
+2025-09-26 10:03:24 [background-preinit] INFO  o.h.validator.internal.util.Version - HV000001: Hibernate Validator 6.0.22.Final
+2025-09-26 10:03:25 [main] INFO  c.z.SaaaMarketingApplication - The following profiles are active: dev
+2025-09-26 10:03:26 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-6088"]
+2025-09-26 10:03:26 [main] INFO  o.a.catalina.core.StandardService - Starting service [Tomcat]
+2025-09-26 10:03:26 [main] INFO  o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.41]
+2025-09-26 10:03:26 [main] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
+2025-09-26 10:03:27 [main] INFO  c.a.d.s.b.a.DruidDataSourceAutoConfigure - Init DruidDataSource
+2025-09-26 10:03:28 [main] INFO  c.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
+2025-09-26 10:03:28 [main] WARN  c.b.m.core.metadata.TableInfoHelper - Can not find table primary key in Class: "com.zhongzheng.domian.SysUserRole".
+2025-09-26 10:03:28 [main] WARN  c.b.m.core.metadata.TableInfoHelper - Can not find table primary key in Class: "com.zhongzheng.domian.SysRoleMenu".
+2025-09-26 10:03:30 [main] INFO  s.d.s.w.WebMvcPropertySourcedRequestMappingHandlerMapping - Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2ControllerWebMvc#getDocumentation(String, HttpServletRequest)]
+2025-09-26 10:03:30 [main] WARN  c.n.c.sources.URLConfigurationSource - No URLs will be polled as dynamic configuration sources.
+2025-09-26 10:03:30 [main] INFO  c.n.c.sources.URLConfigurationSource - To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
+2025-09-26 10:03:30 [main] WARN  c.n.c.sources.URLConfigurationSource - No URLs will be polled as dynamic configuration sources.
+2025-09-26 10:03:30 [main] INFO  c.n.c.sources.URLConfigurationSource - To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
+2025-09-26 10:03:30 [main] WARN  o.s.b.a.t.ThymeleafAutoConfiguration$DefaultTemplateResolverConfiguration - Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.p.DocumentationPluginsBootstrapper - Documentation plugins bootstrapped
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.p.DocumentationPluginsBootstrapper - Found 1 custom documentation plugin(s)
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.s.ApiListingReferenceScanner - Scanning for api listing references
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_1
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_1
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_1
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: removeUsingPOST_1
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_1
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_2
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_2
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_2
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_3
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_2
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: removeUsingPOST_2
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_3
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_3
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_4
+2025-09-26 10:03:31 [main] WARN  s.d.s.w.r.p.ParameterDataTypeReader - Trying to infer dataType java.lang.Long[]
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: removeUsingPOST_3
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_3
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_4
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_4
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_5
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_4
+2025-09-26 10:03:31 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-6088"]
+2025-09-26 10:03:31 [main] INFO  c.z.SaaaMarketingApplication - Started SaaaMarketingApplication in 7.653 seconds (JVM running for 8.717)
+2025-09-26 10:03:32 [http-nio-6088-exec-1] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
+2025-09-26 10:03:32 [boundedElastic-7] INFO  io.lettuce.core.EpollProvider - Starting without optional epoll library
+2025-09-26 10:03:32 [boundedElastic-7] INFO  io.lettuce.core.KqueueProvider - Starting without optional kqueue library
+2025-09-26 10:03:32 [registrationTask1] INFO  d.c.b.a.c.r.ApplicationRegistrator - Application registered itself as 372dd0da660a
+2025-09-26 10:05:21 [background-preinit] INFO  o.h.validator.internal.util.Version - HV000001: Hibernate Validator 6.0.22.Final
+2025-09-26 10:05:22 [main] INFO  c.z.SaaaMarketingApplication - The following profiles are active: dev
+2025-09-26 10:05:24 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-6088"]
+2025-09-26 10:05:24 [main] INFO  o.a.catalina.core.StandardService - Starting service [Tomcat]
+2025-09-26 10:05:24 [main] INFO  o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.41]
+2025-09-26 10:05:24 [main] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
+2025-09-26 10:05:24 [main] INFO  c.a.d.s.b.a.DruidDataSourceAutoConfigure - Init DruidDataSource
+2025-09-26 10:05:25 [main] INFO  c.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
+2025-09-26 10:05:26 [main] WARN  c.b.m.core.metadata.TableInfoHelper - Can not find table primary key in Class: "com.zhongzheng.domian.SysUserRole".
+2025-09-26 10:05:26 [main] WARN  c.b.m.core.metadata.TableInfoHelper - Can not find table primary key in Class: "com.zhongzheng.domian.SysRoleMenu".
+2025-09-26 10:05:27 [main] INFO  s.d.s.w.WebMvcPropertySourcedRequestMappingHandlerMapping - Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2ControllerWebMvc#getDocumentation(String, HttpServletRequest)]
+2025-09-26 10:05:27 [main] WARN  c.n.c.sources.URLConfigurationSource - No URLs will be polled as dynamic configuration sources.
+2025-09-26 10:05:27 [main] INFO  c.n.c.sources.URLConfigurationSource - To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
+2025-09-26 10:05:27 [main] WARN  c.n.c.sources.URLConfigurationSource - No URLs will be polled as dynamic configuration sources.
+2025-09-26 10:05:27 [main] INFO  c.n.c.sources.URLConfigurationSource - To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
+2025-09-26 10:05:27 [main] WARN  o.s.b.a.t.ThymeleafAutoConfiguration$DefaultTemplateResolverConfiguration - Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.p.DocumentationPluginsBootstrapper - Documentation plugins bootstrapped
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.p.DocumentationPluginsBootstrapper - Found 1 custom documentation plugin(s)
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.s.ApiListingReferenceScanner - Scanning for api listing references
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_1
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_1
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_1
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: removeUsingPOST_1
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_1
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_2
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_2
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_2
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_3
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_2
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: removeUsingPOST_2
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_3
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_3
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_4
+2025-09-26 10:05:28 [main] WARN  s.d.s.w.r.p.ParameterDataTypeReader - Trying to infer dataType java.lang.Long[]
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: removeUsingPOST_3
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_3
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_4
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_4
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_5
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_4
+2025-09-26 10:05:28 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-6088"]
+2025-09-26 10:05:29 [main] INFO  c.z.SaaaMarketingApplication - Started SaaaMarketingApplication in 7.555 seconds (JVM running for 8.41)
+2025-09-26 10:05:29 [http-nio-6088-exec-1] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
+2025-09-26 10:05:29 [boundedElastic-7] INFO  io.lettuce.core.EpollProvider - Starting without optional epoll library
+2025-09-26 10:05:29 [boundedElastic-7] INFO  io.lettuce.core.KqueueProvider - Starting without optional kqueue library
+2025-09-26 10:05:29 [registrationTask1] INFO  d.c.b.a.c.r.ApplicationRegistrator - Application registered itself as 372dd0da660a
+2025-09-26 10:09:53 [http-nio-6088-exec-4] WARN  c.a.d.pool.DruidAbstractDataSource - discard long time none received connection. , jdbcUrl : jdbc:mysql://192.168.1.222:3306/zz_edu_marketing?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true, version : 1.2.4, lastPacketReceivedIdleMillis : 268084
+2025-09-26 10:09:53 [http-nio-6088-exec-3] WARN  c.a.d.pool.DruidAbstractDataSource - discard long time none received connection. , jdbcUrl : jdbc:mysql://192.168.1.222:3306/zz_edu_marketing?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true, version : 1.2.4, lastPacketReceivedIdleMillis : 268119
+2025-09-26 10:09:53 [http-nio-6088-exec-3] WARN  c.a.d.pool.DruidAbstractDataSource - discard long time none received connection. , jdbcUrl : jdbc:mysql://192.168.1.222:3306/zz_edu_marketing?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true, version : 1.2.4, lastPacketReceivedIdleMillis : 268165
+2025-09-26 10:09:53 [http-nio-6088-exec-3] WARN  c.a.d.pool.DruidAbstractDataSource - discard long time none received connection. , jdbcUrl : jdbc:mysql://192.168.1.222:3306/zz_edu_marketing?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true, version : 1.2.4, lastPacketReceivedIdleMillis : 268203

+ 0 - 0
logs/sys-error.log


+ 80 - 0
logs/sys-info.log

@@ -0,0 +1,80 @@
+2025-09-26 10:03:24 [background-preinit] INFO  o.h.validator.internal.util.Version - HV000001: Hibernate Validator 6.0.22.Final
+2025-09-26 10:03:25 [main] INFO  c.z.SaaaMarketingApplication - The following profiles are active: dev
+2025-09-26 10:03:26 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-6088"]
+2025-09-26 10:03:26 [main] INFO  o.a.catalina.core.StandardService - Starting service [Tomcat]
+2025-09-26 10:03:26 [main] INFO  o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.41]
+2025-09-26 10:03:26 [main] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
+2025-09-26 10:03:27 [main] INFO  c.a.d.s.b.a.DruidDataSourceAutoConfigure - Init DruidDataSource
+2025-09-26 10:03:28 [main] INFO  c.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
+2025-09-26 10:03:30 [main] INFO  s.d.s.w.WebMvcPropertySourcedRequestMappingHandlerMapping - Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2ControllerWebMvc#getDocumentation(String, HttpServletRequest)]
+2025-09-26 10:03:30 [main] INFO  c.n.c.sources.URLConfigurationSource - To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
+2025-09-26 10:03:30 [main] INFO  c.n.c.sources.URLConfigurationSource - To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.p.DocumentationPluginsBootstrapper - Documentation plugins bootstrapped
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.p.DocumentationPluginsBootstrapper - Found 1 custom documentation plugin(s)
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.s.ApiListingReferenceScanner - Scanning for api listing references
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_1
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_1
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_1
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: removeUsingPOST_1
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_1
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_2
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_2
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_2
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_3
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_2
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: removeUsingPOST_2
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_3
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_3
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_4
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: removeUsingPOST_3
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_3
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_4
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_4
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_5
+2025-09-26 10:03:31 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_4
+2025-09-26 10:03:31 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-6088"]
+2025-09-26 10:03:31 [main] INFO  c.z.SaaaMarketingApplication - Started SaaaMarketingApplication in 7.653 seconds (JVM running for 8.717)
+2025-09-26 10:03:32 [http-nio-6088-exec-1] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
+2025-09-26 10:03:32 [boundedElastic-7] INFO  io.lettuce.core.EpollProvider - Starting without optional epoll library
+2025-09-26 10:03:32 [boundedElastic-7] INFO  io.lettuce.core.KqueueProvider - Starting without optional kqueue library
+2025-09-26 10:03:32 [registrationTask1] INFO  d.c.b.a.c.r.ApplicationRegistrator - Application registered itself as 372dd0da660a
+2025-09-26 10:05:21 [background-preinit] INFO  o.h.validator.internal.util.Version - HV000001: Hibernate Validator 6.0.22.Final
+2025-09-26 10:05:22 [main] INFO  c.z.SaaaMarketingApplication - The following profiles are active: dev
+2025-09-26 10:05:24 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-6088"]
+2025-09-26 10:05:24 [main] INFO  o.a.catalina.core.StandardService - Starting service [Tomcat]
+2025-09-26 10:05:24 [main] INFO  o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.41]
+2025-09-26 10:05:24 [main] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
+2025-09-26 10:05:24 [main] INFO  c.a.d.s.b.a.DruidDataSourceAutoConfigure - Init DruidDataSource
+2025-09-26 10:05:25 [main] INFO  c.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
+2025-09-26 10:05:27 [main] INFO  s.d.s.w.WebMvcPropertySourcedRequestMappingHandlerMapping - Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2ControllerWebMvc#getDocumentation(String, HttpServletRequest)]
+2025-09-26 10:05:27 [main] INFO  c.n.c.sources.URLConfigurationSource - To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
+2025-09-26 10:05:27 [main] INFO  c.n.c.sources.URLConfigurationSource - To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.p.DocumentationPluginsBootstrapper - Documentation plugins bootstrapped
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.p.DocumentationPluginsBootstrapper - Found 1 custom documentation plugin(s)
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.s.ApiListingReferenceScanner - Scanning for api listing references
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_1
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_1
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_1
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: removeUsingPOST_1
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_1
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_2
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_2
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_2
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_3
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_2
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: removeUsingPOST_2
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_3
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_3
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_4
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: removeUsingPOST_3
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_3
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: addUsingPOST_4
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: editUsingPOST_4
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: getInfoUsingGET_5
+2025-09-26 10:05:28 [main] INFO  s.d.s.w.r.o.CachingOperationNameGenerator - Generating unique operation named: listUsingGET_4
+2025-09-26 10:05:28 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-6088"]
+2025-09-26 10:05:29 [main] INFO  c.z.SaaaMarketingApplication - Started SaaaMarketingApplication in 7.555 seconds (JVM running for 8.41)
+2025-09-26 10:05:29 [http-nio-6088-exec-1] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
+2025-09-26 10:05:29 [boundedElastic-7] INFO  io.lettuce.core.EpollProvider - Starting without optional epoll library
+2025-09-26 10:05:29 [boundedElastic-7] INFO  io.lettuce.core.KqueueProvider - Starting without optional kqueue library
+2025-09-26 10:05:29 [registrationTask1] INFO  d.c.b.a.c.r.ApplicationRegistrator - Application registered itself as 372dd0da660a

+ 0 - 0
logs/sys-user.log


+ 358 - 0
pom.xml

@@ -0,0 +1,358 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>sass_marketing</groupId>
+    <artifactId>sass_marketing</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+
+    <name>sass_marketing</name>
+    <description>中正营销系统</description>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <spring-boot.version>2.2.13.RELEASE</spring-boot.version>
+        <oshi.version>5.6.0</oshi.version>
+        <jna.version>5.7.0</jna.version>
+        <poi.version>4.1.2</poi.version>
+        <druid.version>1.2.4</druid.version>
+        <knife4j.version>2.0.8</knife4j.version>
+        <velocity.version>1.7</velocity.version>
+        <fastjson.version>1.2.75</fastjson.version>
+        <jwt.version>0.9.1</jwt.version>
+        <mybatis-plus.version>3.4.2</mybatis-plus.version>
+        <feign.version>2.2.6.RELEASE</feign.version>
+        <feign-okhttp.version>11.0</feign-okhttp.version>
+        <hutool.version>5.5.8</hutool.version>
+        <spring-boot-admin.version>2.3.1</spring-boot-admin.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- SpringBoot Web容器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!--阿里数据库连接池 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>${druid.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>de.codecentric</groupId>
+            <artifactId>spring-boot-admin-starter-server</artifactId>
+            <version>${spring-boot-admin.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>de.codecentric</groupId>
+            <artifactId>spring-boot-admin-starter-client</artifactId>
+            <version>${spring-boot-admin.version}</version>
+        </dependency>
+
+        <!-- 获取系统信息 -->
+        <dependency>
+            <groupId>com.github.oshi</groupId>
+            <artifactId>oshi-core</artifactId>
+            <version>${oshi.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna</artifactId>
+            <version>${jna.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna-platform</artifactId>
+            <version>${jna.version}</version>
+        </dependency>
+
+        <!-- 自定义验证注解 -->
+        <dependency>
+            <groupId>javax.validation</groupId>
+            <artifactId>validation-api</artifactId>
+        </dependency>
+
+        <!-- pool 对象池 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity</artifactId>
+            <version>${velocity.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.mail</groupId>
+            <artifactId>mail</artifactId>
+            <version>1.4.7</version>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- excel工具 -->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>4.1.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>4.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-scratchpad</artifactId>
+            <version>4.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml-schemas</artifactId>
+            <version>4.1.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.afterturn</groupId>
+            <artifactId>easypoi-spring-boot-starter</artifactId>
+            <version>4.1.0</version>
+        </dependency>
+
+        <!-- spring security 安全认证 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <!-- redis 缓存操作 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <!-- 二维码 -->
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.3.0</version>
+        </dependency>
+
+        <!-- OSS SDK 相关依赖 -->
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>3.15.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dysmsapi20170525</artifactId>
+            <version>2.0.7</version>
+        </dependency>
+
+        <!--常用工具类 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <!-- JSON工具类 -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>dom4j</groupId>
+            <artifactId>dom4j</artifactId>
+            <version>1.6.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.thoughtworks.xstream</groupId>
+            <artifactId>xstream</artifactId>
+            <version>1.4.17</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+            <version>${knife4j.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity</artifactId>
+            <version>${velocity.version}</version>
+        </dependency>
+
+        <!-- 阿里JSON解析器 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>${fastjson.version}</version>
+        </dependency>
+
+        <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <!--Token生成与解析-->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>${jwt.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${mybatis-plus.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-extension</artifactId>
+            <version>${mybatis-plus.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>${hutool.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+            <version>${feign.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.github.openfeign</groupId>
+            <artifactId>feign-okhttp</artifactId>
+            <version>${feign-okhttp.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+        </plugins>
+        <resources>
+            <resource>
+                <!--打包该目录下的 application.yml -->
+                <directory>src/main/resources</directory>
+                <!-- 启用过滤 即该资源中的变量将会被过滤器中的值替换 -->
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+    </build>
+
+    <repositories>
+        <repository>
+            <id>public</id>
+            <name>aliyun nexus</name>
+            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+        </repository>
+        <repository>
+            <id>com.e-iceblue</id>
+            <url>http://repo.e-iceblue.cn/repository/maven-public/</url>
+        </repository>
+    </repositories>
+
+    <pluginRepositories>
+        <pluginRepository>
+            <id>public</id>
+            <name>aliyun nexus</name>
+            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </pluginRepository>
+    </pluginRepositories>
+
+    <profiles>
+        <profile>
+            <id>dev</id>
+            <properties>
+                <!-- 环境标识,需要与配置文件的名称相对应 -->
+                <profiles.active>dev</profiles.active>
+                <logging.level>debug</logging.level>
+            </properties>
+            <activation>
+                <!-- 默认环境 -->
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+        <profile>
+            <id>prod</id>
+            <properties>
+                <profiles.active>prod</profiles.active>
+                <logging.level>warn</logging.level>
+            </properties>
+        </profile>
+    </profiles>
+
+</project>

+ 19 - 0
src/main/java/com/zhongzheng/SaaaMarketingApplication.java

@@ -0,0 +1,19 @@
+package com.zhongzheng;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
+@EnableScheduling
+@EnableTransactionManagement
+public class SaaaMarketingApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(SaaaMarketingApplication.class, args);
+        System.out.println("(♥◠‿◠)ノ゙  中正营销启动成功   ლ(´ڡ`ლ)゙"+"ZSADMIN");
+    }
+
+}

+ 18 - 0
src/main/java/com/zhongzheng/SassMarketingServletInitializer.java

@@ -0,0 +1,18 @@
+package com.zhongzheng;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+/**
+ * web容器中进行部署
+ *
+ * @author zhongzheng
+ */
+public class SassMarketingServletInitializer extends SpringBootServletInitializer
+{
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
+    {
+        return application.sources(SaaaMarketingApplication.class);
+    }
+}

+ 25 - 0
src/main/java/com/zhongzheng/bo/OssCallbackParam.java

@@ -0,0 +1,25 @@
+package com.zhongzheng.bo;
+
+import lombok.Data;
+
+/**
+ * oss上传成功后的回调参数
+ */
+@Data
+public class OssCallbackParam {
+    /**
+     *
+        请求的回调地址
+     */
+    private String callbackUrl;
+    /**
+     *
+        回调是传入request中的参数
+     */
+    private String callbackBody;
+    /**
+        回调时传入参数的格式,比如表单提交形式
+    */
+    private String callbackBodyType;
+
+}

+ 35 - 0
src/main/java/com/zhongzheng/bo/OssRequest.java

@@ -0,0 +1,35 @@
+package com.zhongzheng.bo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author change
+ * @date 2021年06月10日 9:07
+ */
+@Data
+@ApiModel("传图片标识")
+public class OssRequest {
+
+    /** 上传图片标识 */
+    @ApiModelProperty(value = "上传图片标识 0头像 1身份证 2题库 3指南指引图片 4广告图片 5身份证或学信网图片 6文件excel,word,zip等 10文件zip",required = true)
+    @NotNull(message = "上传图片标识不能为空")
+    private Integer ImageStatus;
+
+    /** 上传图片 */
+    @ApiModelProperty("MultipartFile上传文件")
+    private MultipartFile file;
+
+    @ApiModelProperty("用户ID")
+    private Long userId;
+
+    @ApiModelProperty("班级ID")
+    private Long gradeId;
+
+    @ApiModelProperty("图片路径")
+    private String path;
+}

+ 43 - 0
src/main/java/com/zhongzheng/bo/SysDictDataBo.java

@@ -0,0 +1,43 @@
+package com.zhongzheng.bo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 字典数据表 sys_dict_data
+ *
+ * @author zhongzheng
+ */
+
+@Data
+public class SysDictDataBo implements Serializable {
+    /** 分页大小 */
+    @ApiModelProperty("分页大小")
+    private Integer pageSize;
+    /** 当前页数 */
+    @ApiModelProperty("当前页数")
+    private Integer pageNum;
+    /** 排序列 */
+    @ApiModelProperty("排序列")
+    private String orderByColumn;
+    /** 排序的方向desc或者asc */
+    @ApiModelProperty(value = "排序的方向", example = "asc,desc")
+    private String isAsc;
+
+    /** 字典标签 */
+    @ApiModelProperty(value ="字典标签")
+    private String dictLabel;
+
+    /** 字典类型 */
+    @ApiModelProperty(value ="字典类型")
+    private String dictType;
+
+    @ApiModelProperty(value ="状态(1正常 0停用)")
+    private String status;
+
+    @ApiModelProperty(value ="字典类型ID")
+    private Long dictTypeId;
+
+}

+ 41 - 0
src/main/java/com/zhongzheng/bo/SysDictTypeBo.java

@@ -0,0 +1,41 @@
+package com.zhongzheng.bo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 字典类型表 sys_dict_type
+ *
+ * @author zhongzheng
+ */
+
+@Data
+public class SysDictTypeBo implements Serializable {
+
+    /** 分页大小 */
+    @ApiModelProperty("分页大小")
+    private Integer pageSize;
+    /** 当前页数 */
+    @ApiModelProperty("当前页数")
+    private Integer pageNum;
+    /** 排序列 */
+    @ApiModelProperty("排序列")
+    private String orderByColumn;
+    /** 排序的方向desc或者asc */
+    @ApiModelProperty(value = "排序的方向", example = "asc,desc")
+    private String isAsc;
+
+    @ApiModelProperty(value ="状态(1正常 0停用)")
+    private String status;
+
+    @ApiModelProperty(value ="字典名称")
+    private String dictName;
+
+    /** 字典类型 */
+    @ApiModelProperty(value ="字典类型")
+    private String dictType;
+
+
+}

+ 67 - 0
src/main/java/com/zhongzheng/bo/SysUserEditBo.java

@@ -0,0 +1,67 @@
+package com.zhongzheng.bo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 用户对象 SysUserEditBo
+ *
+ * @author zhongzheng
+ */
+
+@Data
+@ApiModel("考试安排,包含考试指引编辑对象")
+public class SysUserEditBo
+{
+
+    /** 用户ID */
+    @ApiModelProperty("用户编号")
+    private Long userId;
+
+    /** 部门ID */
+    @ApiModelProperty("部门编号")
+    private Long deptId;
+
+    /** 用户账号 */
+    @ApiModelProperty("用户账号")
+    private String userName;
+
+    /** 用户昵称 */
+    @ApiModelProperty("用户昵称")
+    private String nickName;
+
+    /** 用户邮箱 */
+    @ApiModelProperty("用户邮箱")
+    private String email;
+
+    /** 手机号码 */
+    @ApiModelProperty("手机号码")
+    private String phonenumber;
+
+    @ApiModelProperty("用户性别")
+    private String sex;
+
+    /** 用户头像 */
+    @ApiModelProperty("用户头像")
+    private String avatar;
+
+    /** 密码 */
+    @ApiModelProperty("密码")
+    private String password;
+
+    /** 旧密码 */
+    @ApiModelProperty("旧密码")
+    private String oldPassword;
+
+
+    @ApiModelProperty("帐号状态(1正常 0停用)")
+    private String status;
+    @ApiModelProperty("角色组")
+    private Long[] roleIds;
+    @ApiModelProperty("岗位组")
+    private Long[] postIds;
+    /** 身份证号 */
+    @ApiModelProperty("身份证号")
+    private String idCard;
+}

+ 23 - 0
src/main/java/com/zhongzheng/bo/UserMarketingLoginBo.java

@@ -0,0 +1,23 @@
+package com.zhongzheng.bo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+
+/**
+ * 客户端用户编辑对象 user
+ *
+ * @author ruoyi
+ * @date 2021-06-08
+ */
+@Data
+@ApiModel("客户端用户编辑对象")
+public class UserMarketingLoginBo {
+
+
+    /** 密码 */
+    @ApiModelProperty("密码")
+    private String pwd;
+
+}

+ 165 - 0
src/main/java/com/zhongzheng/common/annotation/Excel.java

@@ -0,0 +1,165 @@
+package com.zhongzheng.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+
+/**
+ * 自定义导出Excel数据注解
+ * 
+ * @author zhongzheng
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Excel
+{
+    /**
+     * 导出时在excel中排序
+     */
+    public int sort() default Integer.MAX_VALUE;
+
+    /**
+     * 导出到Excel中的名字.
+     */
+    public String name() default "";
+
+    /**
+     * 日期格式, 如: yyyy-MM-dd
+     */
+    public String dateFormat() default "";
+
+    /**
+     * 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
+     */
+    public String dictType() default "";
+
+    /**
+     * 读取内容转表达式 (如: 0=男,1=女,2=未知)
+     */
+    public String readConverterExp() default "";
+
+    /**
+     * 分隔符,读取字符串组内容
+     */
+    public String separator() default ",";
+
+    /**
+     * BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化)
+     */
+    public int scale() default -1;
+
+    /**
+     * BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
+     */
+    public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
+
+    /**
+     * 导出类型(0数字 1字符串)
+     */
+    public ColumnType cellType() default ColumnType.STRING;
+
+    /**
+     * 导出时在excel中每个列的高度 单位为字符
+     */
+    public double height() default 14;
+
+    /**
+     * 导出时在excel中每个列的宽 单位为字符
+     */
+    public double width() default 16;
+
+    /**
+     * 文字后缀,如% 90 变成90%
+     */
+    public String suffix() default "";
+
+    /**
+     * 当值为空时,字段的默认值
+     */
+    public String defaultValue() default "";
+
+    /**
+     * 提示信息
+     */
+    public String prompt() default "";
+
+    /**
+     * 设置只能选择不能输入的列内容.
+     */
+    public String[] combo() default {};
+
+    /**
+     * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写.
+     */
+    public boolean isExport() default true;
+
+    /**
+     * 另一个类中的属性名称,支持多级获取,以小数点隔开
+     */
+    public String targetAttr() default "";
+
+    /**
+     * 是否自动统计数据,在最后追加一行统计数据总和
+     */
+    public boolean isStatistics() default false;
+
+    /**
+     * 导出字段对齐方式(0:默认;1:靠左;2:居中;3:靠右)
+     */
+    Align align() default Align.AUTO;
+
+    public enum Align
+    {
+        AUTO(0), LEFT(1), CENTER(2), RIGHT(3);
+        private final int value;
+
+        Align(int value)
+        {
+            this.value = value;
+        }
+
+        public int value()
+        {
+            return this.value;
+        }
+    }
+
+    /**
+     * 字段类型(0:导出导入;1:仅导出;2:仅导入)
+     */
+    Type type() default Type.ALL;
+
+    public enum Type
+    {
+        ALL(0), EXPORT(1), IMPORT(2);
+        private final int value;
+
+        Type(int value)
+        {
+            this.value = value;
+        }
+
+        public int value()
+        {
+            return this.value;
+        }
+    }
+
+    public enum ColumnType
+    {
+        NUMERIC(0), STRING(1), IMAGE(2);
+        private final int value;
+
+        ColumnType(int value)
+        {
+            this.value = value;
+        }
+
+        public int value()
+        {
+            return this.value;
+        }
+    }
+}

+ 18 - 0
src/main/java/com/zhongzheng/common/annotation/Excels.java

@@ -0,0 +1,18 @@
+package com.zhongzheng.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Excel注解集
+ * 
+ * @author zhongzheng
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Excels
+{
+    Excel[] value();
+}

+ 18 - 0
src/main/java/com/zhongzheng/common/annotation/RepeatSubmit.java

@@ -0,0 +1,18 @@
+package com.zhongzheng.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义注解防止表单重复提交
+ * 
+ * @author zhongzheng
+ *
+ */
+@Inherited
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RepeatSubmit
+{
+
+}

+ 24 - 0
src/main/java/com/zhongzheng/common/config/AdminServerConfig.java

@@ -0,0 +1,24 @@
+package com.zhongzheng.common.config;
+
+import de.codecentric.boot.admin.server.config.EnableAdminServer;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
+import org.springframework.boot.task.TaskExecutorBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+
+@Configuration
+@EnableAdminServer
+public class AdminServerConfig {
+
+    @Lazy
+    @Bean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
+    @ConditionalOnMissingBean(Executor.class)
+    public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
+        return builder.build();
+    }
+}

+ 58 - 0
src/main/java/com/zhongzheng/common/config/CaptchaConfig.java

@@ -0,0 +1,58 @@
+package com.zhongzheng.common.config;
+
+import cn.hutool.captcha.CaptchaUtil;
+import cn.hutool.captcha.CircleCaptcha;
+import cn.hutool.captcha.LineCaptcha;
+import cn.hutool.captcha.ShearCaptcha;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.awt.*;
+
+/**
+ * 验证码配置
+ *
+ * @author Lion Li
+ */
+@Configuration
+public class CaptchaConfig {
+
+    private final int width = 160;
+    private final int height = 60;
+    private final Color background = Color.PINK;
+    private final Font font = new Font("Arial", Font.BOLD, 48);
+
+    /**
+     * 圆圈干扰验证码
+     */
+    @Bean(name = "CircleCaptcha")
+    public CircleCaptcha getCircleCaptcha() {
+        CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(width, height);
+        captcha.setBackground(background);
+        captcha.setFont(font);
+        return captcha;
+    }
+
+    /**
+     * 线段干扰的验证码
+     */
+    @Bean(name = "LineCaptcha")
+    public LineCaptcha getLineCaptcha() {
+        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(width, height);
+        captcha.setBackground(background);
+        captcha.setFont(font);
+        return captcha;
+    }
+
+    /**
+     * 扭曲干扰验证码
+     */
+    @Bean(name = "ShearCaptcha")
+    public ShearCaptcha getShearCaptcha() {
+        ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(width, height);
+        captcha.setBackground(background);
+        captcha.setFont(font);
+        return captcha;
+    }
+
+}

+ 61 - 0
src/main/java/com/zhongzheng/common/config/FilterConfig.java

@@ -0,0 +1,61 @@
+package com.zhongzheng.common.config;
+
+import cn.hutool.core.util.StrUtil;
+import com.zhongzheng.common.filter.RepeatableFilter;
+import com.zhongzheng.common.filter.XssFilter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.DispatcherType;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Filter配置
+ *
+ * @author zhongzheng
+ */
+@Configuration
+public class FilterConfig
+{
+    @Value("${xss.enabled}")
+    private String enabled;
+
+    @Value("${xss.excludes}")
+    private String excludes;
+
+    @Value("${xss.urlPatterns}")
+    private String urlPatterns;
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Bean
+    public FilterRegistrationBean xssFilterRegistration()
+    {
+        FilterRegistrationBean registration = new FilterRegistrationBean();
+        registration.setDispatcherTypes(DispatcherType.REQUEST);
+        registration.setFilter(new XssFilter());
+        registration.addUrlPatterns(StrUtil.split(urlPatterns, ","));
+        registration.setName("xssFilter");
+        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
+        Map<String, String> initParameters = new HashMap<String, String>();
+        initParameters.put("excludes", excludes);
+        initParameters.put("enabled", enabled);
+        registration.setInitParameters(initParameters);
+        return registration;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Bean
+    public FilterRegistrationBean someFilterRegistration()
+    {
+        FilterRegistrationBean registration = new FilterRegistrationBean();
+        registration.setFilter(new RepeatableFilter());
+        registration.addUrlPatterns("/*");
+        registration.setName("repeatableFilter");
+        registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
+        return registration;
+    }
+
+}

+ 111 - 0
src/main/java/com/zhongzheng/common/config/MybatisPlusConfig.java

@@ -0,0 +1,111 @@
+package com.zhongzheng.common.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
+import com.zhongzheng.common.croe.CreateAndUpdateMetaObjectHandler;
+import com.zhongzheng.common.croe.CustomTenantLineHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+@EnableTransactionManagement(proxyTargetClass = true)
+@Configuration
+public class MybatisPlusConfig {
+
+	@Autowired
+	private CustomTenantLineHandler customTenantLineHandler;
+
+	@Bean
+	public MybatisPlusInterceptor mybatisPlusInterceptor() {
+		MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+		// 多租户插件(注意:这个一定要放在最上面)
+		interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(customTenantLineHandler));
+		// 分页插件
+		interceptor.addInnerInterceptor(paginationInnerInterceptor());
+		// 乐观锁插件
+		interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
+		// 阻断插件
+//		interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
+		return interceptor;
+	}
+
+	/**
+	 * 分页插件,自动识别数据库类型
+	 * https://baomidou.com/guide/interceptor-pagination.html
+	 */
+	public PaginationInnerInterceptor paginationInnerInterceptor() {
+		PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
+
+		// 设置数据库类型为mysql
+		paginationInnerInterceptor.setDbType(DbType.MYSQL);
+		// 设置最大单页限制数量,默认 500 条,-1 不受限制
+		paginationInnerInterceptor.setMaxLimit(-1L);
+
+		return paginationInnerInterceptor;
+	}
+
+	/**
+	 * 乐观锁插件
+	 * https://baomidou.com/guide/interceptor-optimistic-locker.html
+	 */
+	public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
+		return new OptimisticLockerInnerInterceptor();
+	}
+
+	/**
+	 * 如果是对全表的删除或更新操作,就会终止该操作
+	 * https://baomidou.com/guide/interceptor-block-attack.html
+	 */
+//	public BlockAttackInnerInterceptor blockAttackInnerInterceptor() {
+//		return new BlockAttackInnerInterceptor();
+//	}
+
+	/**
+	 * sql性能规范插件(垃圾SQL拦截)
+	 * 如有需要可以启用
+	 */
+//	public IllegalSQLInnerInterceptor illegalSQLInnerInterceptor() {
+//		return new IllegalSQLInnerInterceptor();
+//	}
+
+
+	/**
+	 * 自定义主键策略
+	 * https://baomidou.com/guide/id-generator.html
+	 */
+//	@Bean
+//	public IdentifierGenerator idGenerator() {
+//		return new CustomIdGenerator();
+//	}
+
+	/**
+	 * 元对象字段填充控制器
+	 * https://baomidou.com/guide/auto-fill-metainfo.html
+	 */
+	@Bean
+	public MetaObjectHandler metaObjectHandler() {
+		return new CreateAndUpdateMetaObjectHandler();
+	}
+
+	/**
+	 * sql注入器配置
+	 * https://baomidou.com/guide/sql-injector.html
+	 */
+//	@Bean
+//	public ISqlInjector sqlInjector() {
+//		return new DefaultSqlInjector();
+//	}
+
+	/**
+	 * TenantLineInnerInterceptor 多租户插件
+	 * https://baomidou.com/guide/interceptor-tenant-line.html
+	 * DynamicTableNameInnerInterceptor 动态表名插件
+	 * https://baomidou.com/guide/interceptor-dynamic-table-name.html
+	 */
+
+}

+ 20 - 0
src/main/java/com/zhongzheng/common/config/OssConfig.java

@@ -0,0 +1,20 @@
+package com.zhongzheng.common.config;
+
+import com.aliyun.oss.OSSClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class OssConfig {
+    @Value("${aliyun.oss.endpoint}")
+    private String ALIYUN_OSS_ENDPOINT;
+    @Value("${aliyun.oss.accessKeyId}")
+    private String ALIYUN_OSS_ACCESSKEYID;
+    @Value("${aliyun.oss.accessKeySecret}")
+    private String ALIYUN_OSS_ACCESSKEYSECRET;
+    @Bean
+    public OSSClient ossClient(){
+        return new OSSClient(ALIYUN_OSS_ENDPOINT,ALIYUN_OSS_ACCESSKEYID,ALIYUN_OSS_ACCESSKEYSECRET);
+    }
+}

+ 46 - 0
src/main/java/com/zhongzheng/common/config/RedisConfig.java

@@ -0,0 +1,46 @@
+package com.zhongzheng.common.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
+import com.zhongzheng.common.croe.FastJson2JsonRedisSerializer;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * redis配置
+ * 
+ * @author zhongzheng
+ */
+@Configuration
+@EnableCaching
+public class RedisConfig extends CachingConfigurerSupport
+{
+    @Bean
+    @SuppressWarnings(value = { "unchecked", "rawtypes" })
+    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
+    {
+        RedisTemplate<Object, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
+
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
+        serializer.setObjectMapper(mapper);
+
+        template.setValueSerializer(serializer);
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.afterPropertiesSet();
+        return template;
+    }
+}

+ 66 - 0
src/main/java/com/zhongzheng/common/config/ResourcesConfig.java

@@ -0,0 +1,66 @@
+package com.zhongzheng.common.config;
+
+
+import com.zhongzheng.common.filter.Constants;
+import com.zhongzheng.common.interceptor.RepeatSubmitInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * 通用配置
+ *
+ * @author zhongzheng
+ */
+@Configuration
+public class ResourcesConfig implements WebMvcConfigurer
+{
+    @Autowired
+    private RepeatSubmitInterceptor repeatSubmitInterceptor;
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry)
+    {
+        /** 本地文件上传路径 */
+        registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
+
+        /** swagger配置 */
+        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
+        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
+    }
+
+    /**
+     * 自定义拦截规则
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry)
+    {
+        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
+    }
+
+    /**
+     * 跨域配置
+     */
+    @Bean
+    public CorsFilter corsFilter()
+    {
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        CorsConfiguration config = new CorsConfiguration();
+        config.setAllowCredentials(true);
+        // 设置访问源地址
+        config.addAllowedOrigin("*");
+        // 设置访问源请求头
+        config.addAllowedHeader("*");
+        // 设置访问源请求方法
+        config.addAllowedMethod("*");
+        // 对接口配置跨域设置
+        source.registerCorsConfiguration("/**", config);
+        return new CorsFilter(source);
+    }
+}

+ 76 - 0
src/main/java/com/zhongzheng/common/config/RuoYiConfig.java

@@ -0,0 +1,76 @@
+package com.zhongzheng.common.config;
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 读取项目相关配置
+ * 
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+@Component
+@ConfigurationProperties(prefix = "zhongzheng")
+public class RuoYiConfig
+{
+    /** 项目名称 */
+    private String name;
+
+    /** 版本 */
+    private String version;
+
+    /** 版权年份 */
+    private String copyrightYear;
+
+    /** 实例演示开关 */
+    private boolean demoEnabled;
+
+    /** 上传路径 */
+    @Getter
+    private static String profile;
+
+    /** 获取地址开关 */
+    @Getter
+    private static boolean addressEnabled;
+
+    public void setProfile(String profile)
+    {
+        RuoYiConfig.profile = profile;
+    }
+
+    public void setAddressEnabled(boolean addressEnabled)
+    {
+        RuoYiConfig.addressEnabled = addressEnabled;
+    }
+
+    /**
+     * 获取头像上传路径
+     */
+    public static String getAvatarPath()
+    {
+        return getProfile() + "/avatar";
+    }
+
+    /**
+     * 获取下载路径
+     */
+    public static String getDownloadPath()
+    {
+        return getProfile() + "/download/";
+    }
+
+    /**
+     * 获取上传路径
+     */
+    public static String getUploadPath()
+    {
+        return getProfile() + "/upload";
+    }
+}

+ 157 - 0
src/main/java/com/zhongzheng/common/config/SecurityConfig.java

@@ -0,0 +1,157 @@
+package com.zhongzheng.common.config;
+
+
+import com.zhongzheng.common.filter.JwtAuthenticationTokenFilter;
+import com.zhongzheng.common.model.AuthenticationEntryPointImpl;
+import com.zhongzheng.common.model.LogoutSuccessHandlerImpl;
+import com.zhongzheng.common.model.UserDetailsServiceImpl;
+import de.codecentric.boot.admin.server.config.AdminServerProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.logout.LogoutFilter;
+import org.springframework.web.filter.CorsFilter;
+
+/**
+ * spring security配置
+ *
+ * @author zhongzheng
+ */
+@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+    /**
+     * 自定义用户认证逻辑
+     */
+    @Autowired
+    private UserDetailsServiceImpl userDetailsService;
+
+    /**
+     * 认证失败处理类
+     */
+    @Autowired
+    private AuthenticationEntryPointImpl unauthorizedHandler;
+
+    /**
+     * 退出处理类
+     */
+    @Autowired
+    @Lazy
+    private LogoutSuccessHandlerImpl logoutSuccessHandler;
+
+    /**
+     * token认证过滤器
+     */
+    @Autowired
+    private JwtAuthenticationTokenFilter authenticationTokenFilter;
+
+    /**
+     * 跨域过滤器
+     */
+    @Autowired
+    private CorsFilter corsFilter;
+
+    @Autowired
+    private AdminServerProperties adminServerProperties;
+
+    /**
+     * 解决 无法直接注入 AuthenticationManager
+     *
+     * @return
+     * @throws Exception
+     */
+    @Bean
+    @Override
+    public AuthenticationManager authenticationManagerBean() throws Exception {
+        return super.authenticationManagerBean();
+    }
+
+    /**
+     * anyRequest          |   匹配所有请求路径
+     * access              |   SpringEl表达式结果为true时可以访问
+     * anonymous           |   匿名可以访问
+     * denyAll             |   用户不能访问
+     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
+     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
+     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
+     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
+     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
+     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
+     * permitAll           |   用户可以任意访问
+     * rememberMe          |   允许通过remember-me登录的用户访问
+     * authenticated       |   用户登录后可访问
+     */
+    @Override
+    protected void configure(HttpSecurity httpSecurity) throws Exception {
+        httpSecurity
+                // CSRF禁用,因为不使用session
+                .csrf().disable()
+                // 认证失败处理类
+                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
+                // 基于token,所以不需要session
+                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
+                // 过滤请求
+                .authorizeRequests()
+                // 对于登录login 验证码captchaImage 允许匿名访问
+                .antMatchers("/login","/captchaImage", "/testLogin", "/scan_code").anonymous()
+                .antMatchers(
+                        HttpMethod.GET,
+                        "/*.html",
+                        "/**/*.html",
+                        "/**/*.css",
+                        "/**/*.js"
+                ).permitAll()
+                .antMatchers("/sys/common/**").anonymous()
+                .antMatchers("/refreshToken/**").anonymous()
+                .antMatchers("/v1/test/**").anonymous()
+                .antMatchers("/profile/**").anonymous()
+                .antMatchers("/swagger-ui.html").anonymous()
+                .antMatchers("/swagger-resources/**").anonymous()
+                .antMatchers("/webjars/**").anonymous()
+                .antMatchers("/*/api-docs").anonymous()
+                .antMatchers("/druid/**").anonymous()
+                // Spring Boot Admin Server 的安全配置
+                .antMatchers(adminServerProperties.getContextPath()).anonymous()
+                .antMatchers(adminServerProperties.getContextPath() + "/**").anonymous()
+                // Spring Boot Actuator 的安全配置
+                .antMatchers("/actuator").anonymous()
+                .antMatchers("/actuator/**").anonymous()
+                .antMatchers("/webSocket/**").anonymous()
+                // 除上面外的所有请求全部需要鉴权认证
+                .anyRequest().authenticated()
+                .and()
+                .headers().frameOptions().disable();
+        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
+        // 添加JWT filter
+        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
+        // 添加CORS filter
+        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
+        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
+    }
+
+
+    /**
+     * 强散列哈希加密实现
+     */
+    @Bean
+    public BCryptPasswordEncoder bCryptPasswordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    /**
+     * 身份认证接口
+     */
+    @Override
+    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
+    }
+}

+ 32 - 0
src/main/java/com/zhongzheng/common/config/ServerConfig.java

@@ -0,0 +1,32 @@
+package com.zhongzheng.common.config;
+
+import com.zhongzheng.common.util.ServletUtils;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 服务相关配置
+ * 
+ * @author zhongzheng
+ */
+@Component
+public class ServerConfig {
+    /**
+     * 获取完整的请求路径,包括:域名,端口,上下文访问路径
+     * 
+     * @return 服务地址
+     */
+    public String getUrl()
+    {
+        HttpServletRequest request = ServletUtils.getRequest();
+        return getDomain(request);
+    }
+
+    public static String getDomain(HttpServletRequest request)
+    {
+        StringBuffer url = request.getRequestURL();
+        String contextPath = request.getServletContext().getContextPath();
+        return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
+    }
+}

+ 148 - 0
src/main/java/com/zhongzheng/common/config/SwaggerConfig.java

@@ -0,0 +1,148 @@
+package com.zhongzheng.common.config;
+
+import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
+import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.ParameterBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.schema.ModelRef;
+import springfox.documentation.service.*;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spi.service.contexts.SecurityContext;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Swagger2的接口配置
+ *
+ * @author zhongzheng
+ */
+@Configuration
+@EnableSwagger2WebMvc
+@EnableKnife4j
+public class SwaggerConfig
+{
+    /** 系统基础配置 */
+    @Autowired
+    private RuoYiConfig ruoyiConfig;
+
+    /** 是否开启swagger */
+    @Value("${swagger.enabled}")
+    private boolean enabled;
+
+    /** 设置请求的统一前缀 */
+    @Value("${swagger.pathMapping}")
+    private String pathMapping;
+
+    private final OpenApiExtensionResolver openApiExtensionResolver;
+
+    /**
+     * 创建API
+     */
+    @Bean
+    public Docket createRestApi()
+    {
+        return new Docket(DocumentationType.SWAGGER_2)
+                // 是否启用Swagger
+                .enable(enabled)
+                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
+                .apiInfo(apiInfo())
+                // 设置哪些接口暴露给Swagger展示
+                .select()
+                // 扫描所有有注解的api,用这种方式更灵活
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                // 扫描指定包中的swagger注解
+                // .apis(RequestHandlerSelectors.basePackage("com.zhongzheng.project.tool.swagger"))
+                // 扫描所有 .apis(RequestHandlerSelectors.any())
+                .paths(PathSelectors.any())
+                .build()
+                /* 设置安全模式,swagger可以设置访问token */
+           //     .securitySchemes(securitySchemes())
+                .securityContexts(securityContexts())
+                .pathMapping(pathMapping)
+                .globalOperationParameters(getGlobalOperationParameters())
+                .extensions(openApiExtensionResolver.buildSettingExtensions());
+    }
+
+    @Autowired
+    public SwaggerConfig(OpenApiExtensionResolver openApiExtensionResolver) {
+        this.openApiExtensionResolver = openApiExtensionResolver;
+    }
+
+    /**
+     * 安全模式,这里指定token通过AuthorizationToken头请求头传递
+     */
+    private List<ApiKey> securitySchemes()
+    {
+        List<ApiKey> apiKeyList = new ArrayList<ApiKey>();
+        return apiKeyList;
+    }
+
+    /**
+     * 安全上下文
+     */
+    private List<SecurityContext> securityContexts()
+    {
+        List<SecurityContext> securityContexts = new ArrayList<>();
+        securityContexts.add(
+                SecurityContext.builder()
+                        .securityReferences(defaultAuth())
+                        .forPaths(PathSelectors.regex("^(?!auth).*$"))
+                        .build());
+        return securityContexts;
+    }
+
+    /**
+     * 默认的安全上引用
+     */
+    private List<SecurityReference> defaultAuth()
+    {
+        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
+        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
+        authorizationScopes[0] = authorizationScope;
+        List<SecurityReference> securityReferences = new ArrayList<>();
+        securityReferences.add(new SecurityReference("AuthorizationToken", authorizationScopes));
+        return securityReferences;
+    }
+
+    /**
+     * 添加摘要信息
+     */
+    private ApiInfo apiInfo()
+    {
+        // 用ApiInfoBuilder进行定制
+        return new ApiInfoBuilder()
+                // 设置标题
+                .title("标题:中正B端管理系统_接口文档")
+                // 描述
+                .description("描述:用于中正B端管理系统_接口文档")
+                // 作者信息
+                .contact(new Contact(ruoyiConfig.getName(), null, null))
+                // 版本
+                .version("版本号:0.0.1")
+
+                .build();
+    }
+
+    private List<Parameter> getGlobalOperationParameters() {
+        List<Parameter> pars = new ArrayList<>();
+        ParameterBuilder parameterBuilder = new ParameterBuilder();
+        // header query cookie
+        parameterBuilder.name("X-Auth-Token").description("token").modelRef(new ModelRef("string")).parameterType("header").defaultValue("test").required(false);
+        pars.add(parameterBuilder.build());
+        ParameterBuilder parameterBuilder1 = new ParameterBuilder();
+        parameterBuilder1.name("companyid").description("companyid").modelRef(new ModelRef("string")).parameterType("header").defaultValue("867735392558919680").required(false);
+        pars.add(parameterBuilder1.build());
+        return pars;
+    }
+
+}

+ 71 - 0
src/main/java/com/zhongzheng/common/config/ThreadPoolConfig.java

@@ -0,0 +1,71 @@
+package com.zhongzheng.common.config;
+
+import com.zhongzheng.common.util.Threads;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 线程池配置
+ *
+ * @author zhongzheng
+ **/
+@Configuration
+@ComponentScan("com.zhongzheng.common.schedule.impl")
+@EnableAsync
+public class ThreadPoolConfig implements AsyncConfigurer
+{
+    // 核心线程池大小
+    private int corePoolSize = 50;
+
+    // 最大可创建的线程数
+    private int maxPoolSize = 200;
+
+    // 队列最大长度
+    private int queueCapacity = 1000;
+
+    // 线程池维护线程所允许的空闲时间
+    private int keepAliveSeconds = 300;
+
+    @Bean(name = "threadPoolTaskExecutor")
+    public ThreadPoolTaskExecutor threadPoolTaskExecutor()
+    {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setMaxPoolSize(maxPoolSize);
+        executor.setCorePoolSize(corePoolSize);
+        executor.setQueueCapacity(queueCapacity);
+        executor.setKeepAliveSeconds(keepAliveSeconds);
+        // 线程池对拒绝任务(无线程可用)的处理策略
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        return executor;
+    }
+
+
+
+    /**
+     * 执行周期性或定时任务
+     */
+    @Bean(name = "scheduledExecutorService")
+    protected ScheduledExecutorService scheduledExecutorService()
+    {
+        return new ScheduledThreadPoolExecutor(corePoolSize,
+                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build())
+        {
+            @Override
+            protected void afterExecute(Runnable r, Throwable t)
+            {
+                super.afterExecute(r, t);
+                Threads.printException(r, t);
+            }
+        };
+    }
+
+}

+ 90 - 0
src/main/java/com/zhongzheng/common/croe/AsyncFactory.java

@@ -0,0 +1,90 @@
+package com.zhongzheng.common.croe;
+
+import cn.hutool.http.useragent.UserAgent;
+import cn.hutool.http.useragent.UserAgentUtil;
+import com.zhongzheng.common.filter.Constants;
+import com.zhongzheng.common.model.SysLogininfor;
+import com.zhongzheng.common.util.AddressUtils;
+import com.zhongzheng.common.util.IpUtils;
+import com.zhongzheng.common.util.ServletUtils;
+import com.zhongzheng.common.util.SpringUtils;
+import com.zhongzheng.service.ISysLogininforService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.TimerTask;
+
+/**
+ * 异步工厂(产生任务用)
+ *
+ * @author zhongzheng
+ */
+public class AsyncFactory
+{
+    private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");
+
+    /**
+     * 记录登录信息
+     *
+     * @param username 用户名
+     * @param status 状态
+     * @param message 消息
+     * @param args 列表
+     * @return 任务task
+     */
+    public static TimerTask recordLogininfor(final String username, final String status, final String message,
+            final Object... args)
+    {
+        final UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
+        final String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+        return new TimerTask()
+        {
+            @Override
+            public void run()
+            {
+                String address = AddressUtils.getRealAddressByIP(ip);
+                StringBuilder s = new StringBuilder();
+                s.append(getBlock(ip));
+                s.append(address);
+                s.append(getBlock(username));
+                s.append(getBlock(status));
+                s.append(getBlock(message));
+                // 打印信息到日志
+//                sys_user_logger.info(s.toString(), args);
+                // 获取客户端操作系统
+                String os = userAgent.getOs().getName();
+                // 获取客户端浏览器
+                String browser = userAgent.getBrowser().getName();
+                // 封装对象
+                SysLogininfor logininfor = new SysLogininfor();
+                logininfor.setUserName(username);
+                logininfor.setIpaddr(ip);
+                logininfor.setLoginLocation(address);
+                logininfor.setBrowser(browser);
+                logininfor.setOs(os);
+                logininfor.setMsg(message);
+                // 日志状态
+                if (Constants.LOGIN_SUCCESS.equals(status) || Constants.LOGOUT.equals(status))
+                {
+                    logininfor.setStatus(Constants.SUCCESS);
+                }
+                else if (Constants.LOGIN_FAIL.equals(status))
+                {
+                    logininfor.setStatus(Constants.FAIL);
+                }
+                // 插入数据
+                SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
+            }
+        };
+    }
+
+
+    public static String getBlock(Object msg)
+    {
+        if (msg == null)
+        {
+            msg = "";
+        }
+        return "[" + msg.toString() + "]";
+    }
+}

+ 57 - 0
src/main/java/com/zhongzheng/common/croe/AsyncManager.java

@@ -0,0 +1,57 @@
+package com.zhongzheng.common.croe;
+
+
+import com.zhongzheng.common.util.SpringUtils;
+import com.zhongzheng.common.util.Threads;
+
+import java.util.TimerTask;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 异步任务管理器
+ * 
+ * @author zhongzheng
+ */
+public class AsyncManager
+{
+    /**
+     * 操作延迟10毫秒
+     */
+    private final int OPERATE_DELAY_TIME = 10;
+
+    /**
+     * 异步操作任务调度线程池
+     */
+    private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
+
+    /**
+     * 单例模式
+     */
+    private AsyncManager(){}
+
+    private static AsyncManager me = new AsyncManager();
+
+    public static AsyncManager me()
+    {
+        return me;
+    }
+
+    /**
+     * 执行任务
+     * 
+     * @param task 任务
+     */
+    public void execute(TimerTask task)
+    {
+        executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * 停止任务线程池
+     */
+    public void shutdown()
+    {
+        Threads.shutdownAndAwaitTermination(executor);
+    }
+}

+ 105 - 0
src/main/java/com/zhongzheng/common/croe/BaseController.java

@@ -0,0 +1,105 @@
+package com.zhongzheng.common.croe;
+
+
+import cn.hutool.core.util.StrUtil;
+import com.zhongzheng.common.model.AjaxResult;
+import com.zhongzheng.common.util.DateUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+
+import java.beans.PropertyEditorSupport;
+import java.util.Date;
+
+/**
+ * web层通用数据处理
+ *
+ * @author zhongzheng
+ */
+public class BaseController
+{
+    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /**
+     * 将前台传递过来的日期格式的字符串,自动转化为Date类型
+     */
+    @InitBinder
+    public void initBinder(WebDataBinder binder)
+    {
+        // Date 类型转换
+        binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
+        {
+            @Override
+            public void setAsText(String text)
+            {
+                setValue(DateUtils.parseDate(text));
+            }
+        });
+    }
+
+
+    /**
+     * 响应返回结果
+     *
+     * @param rows 影响行数
+     * @return 操作结果
+     */
+    protected AjaxResult<Void> toAjax(int rows)
+    {
+        return rows > 0 ? AjaxResult.success() : AjaxResult.error();
+    }
+
+
+
+    /**
+     * 响应返回结果
+     *
+     * @param result 结果
+     * @return 操作结果
+     */
+    protected AjaxResult<Void> toAjax(boolean result)
+    {
+        return result ? success() : error();
+    }
+
+    /**
+     * 返回成功
+     */
+    public AjaxResult<Void> success()
+    {
+        return AjaxResult.success();
+    }
+
+    /**
+     * 返回失败消息
+     */
+    public AjaxResult<Void> error()
+    {
+        return AjaxResult.error();
+    }
+
+    /**
+     * 返回成功消息
+     */
+    public AjaxResult<Void> success(String message)
+    {
+        return AjaxResult.success(message);
+    }
+
+    /**
+     * 返回失败消息
+     */
+    public AjaxResult<Void> error(String message)
+    {
+        return AjaxResult.error(message);
+    }
+
+    /**
+     * 页面跳转
+     */
+    public String redirect(String url)
+    {
+        return StrUtil.format("redirect:{}", url);
+    }
+}

+ 50 - 0
src/main/java/com/zhongzheng/common/croe/CreateAndUpdateMetaObjectHandler.java

@@ -0,0 +1,50 @@
+package com.zhongzheng.common.croe;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.zhongzheng.common.util.DateUtils;
+import com.zhongzheng.common.util.SecurityUtils;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * MP注入处理器
+ * @author Lion Li
+ * @date 2021/4/25
+ */
+public class CreateAndUpdateMetaObjectHandler implements MetaObjectHandler {
+
+	@Value("${mybatis-plus.tenant.enabled-tenant:true}")
+	private boolean enabledTenant;
+
+	@Value("${tianchong:true}")
+	private boolean tianchong;
+	@Override
+	public void insertFill(MetaObject metaObject) {
+		//根据属性名字设置要填充的值
+		if (metaObject.hasGetter("createTime")) {
+			if (metaObject.getValue("createTime") == null && enabledTenant && tianchong) {
+				this.setFieldValByName("createTime", DateUtils.getNowDate(), metaObject);
+			}
+		}
+		if (metaObject.hasGetter("createBy")) {
+			if (metaObject.getValue("createBy") == null && tianchong) {
+				this.setFieldValByName("createBy", SecurityUtils.getUsername(), metaObject);
+			}
+		}
+	}
+
+	@Override
+	public void updateFill(MetaObject metaObject) {
+		if (metaObject.hasGetter("updateBy")) {
+			if (metaObject.getValue("updateBy" ) == null && tianchong) {
+				this.setFieldValByName("updateBy", SecurityUtils.getUsername(), metaObject);
+			}
+		}
+		if (metaObject.hasGetter("updateTime")) {
+			if (metaObject.getValue("updateTime") == null && enabledTenant) {
+				this.setFieldValByName("updateTime", DateUtils.getNowDate(), metaObject);
+			}
+		}
+	}
+
+}

+ 95 - 0
src/main/java/com/zhongzheng/common/croe/CustomTenantLineHandler.java

@@ -0,0 +1,95 @@
+package com.zhongzheng.common.croe;
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.http.HttpStatus;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
+import com.zhongzheng.common.model.AjaxResult;
+import com.zhongzheng.common.util.ServletUtils;
+import lombok.SneakyThrows;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.LongValue;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 多租户处理插件
+ *
+ * @author hjl
+ * @date 2021/07/30 13:37
+ */
+@Component
+public class CustomTenantLineHandler implements TenantLineHandler {
+
+    /**
+     * 租户id字段名
+     */
+    public static final String TENANT_ID_COLUMN = "company_id";
+
+
+    @Value("${mybatis-plus.tenant.enabled-tenant}")
+    private boolean enabledTenant;
+
+
+    /**
+     * 忽略添加租户ID的表
+     */
+    private final static List<String> IGNORE_TABLE_NAMES = new ArrayList<String>() {{
+        add("sys_tenant");
+        add("tables");
+        add("columns");
+        add("sys_logininfor");
+        add("sys_old_org");
+        add("top_sys_tenant_register");
+    }};
+
+    /**
+     * 获取租户ID值表达式(可从cookie、token、缓存中取)
+     *
+     * @return
+     */
+    @SneakyThrows
+    @Override
+    public Expression getTenantId() {
+        String TenantId = ServletUtils.getResponse().getHeader("CompanyId");
+        if (Validator.isEmpty(TenantId)) {
+            TenantId = ServletUtils.getRequest().getHeader("CompanyId");
+            if (enabledTenant) {
+                if (Validator.isEmpty(TenantId)) {
+                    int code = HttpStatus.HTTP_UNAVAILABLE;
+                    String msg = "企业非法访问";
+                    ServletUtils.renderString(ServletUtils.getResponse(), JSON.toJSONString(AjaxResult.error(code, msg)));
+                    ServletUtils.getResponse().getWriter().flush();
+                    ServletUtils.getResponse().getWriter().close();
+                }
+            }
+        }
+        return new LongValue(TenantId);
+    }
+
+    /**
+     * 获取租户字段名(数据库的租户ID字段名)
+     *
+     * @return
+     */
+    @Override
+    public String getTenantIdColumn() {
+        return TENANT_ID_COLUMN;
+    }
+
+    /**
+     * 根据表名判断是否忽略拼接多租户条件
+     *
+     * @param tableName
+     * @return
+     */
+    @Override
+    public boolean ignoreTable(String tableName) {
+        return IGNORE_TABLE_NAMES.contains(tableName);
+    }
+
+
+}

+ 72 - 0
src/main/java/com/zhongzheng/common/croe/FastJson2JsonRedisSerializer.java

@@ -0,0 +1,72 @@
+package com.zhongzheng.common.croe;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.parser.ParserConfig;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+import org.springframework.util.Assert;
+
+import java.nio.charset.Charset;
+
+/**
+ * Redis使用FastJson序列化
+ * 
+ * @author zhongzheng
+ */
+public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
+{
+    @SuppressWarnings("unused")
+    private ObjectMapper objectMapper = new ObjectMapper();
+
+    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+    private Class<T> clazz;
+
+    static
+    {
+        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
+    }
+
+    public FastJson2JsonRedisSerializer(Class<T> clazz)
+    {
+        super();
+        this.clazz = clazz;
+    }
+
+    @Override
+    public byte[] serialize(T t) throws SerializationException
+    {
+        if (t == null)
+        {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+    }
+
+    @Override
+    public T deserialize(byte[] bytes) throws SerializationException
+    {
+        if (bytes == null || bytes.length <= 0)
+        {
+            return null;
+        }
+        String str = new String(bytes, DEFAULT_CHARSET);
+
+        return JSON.parseObject(str, clazz);
+    }
+
+    public void setObjectMapper(ObjectMapper objectMapper)
+    {
+        Assert.notNull(objectMapper, "'objectMapper' must not be null");
+        this.objectMapper = objectMapper;
+    }
+
+    protected JavaType getJavaType(Class<?> clazz)
+    {
+        return TypeFactory.defaultInstance().constructType(clazz);
+    }
+}

+ 56 - 0
src/main/java/com/zhongzheng/common/croe/HttpHelper.java

@@ -0,0 +1,56 @@
+package com.zhongzheng.common.croe;
+
+import cn.hutool.core.exceptions.ExceptionUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletRequest;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+
+/**
+ * 通用http工具封装
+ * 
+ * @author zhongzheng
+ */
+public class HttpHelper
+{
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class);
+
+    public static String getBodyString(ServletRequest request)
+    {
+        StringBuilder sb = new StringBuilder();
+        BufferedReader reader = null;
+        try (InputStream inputStream = request.getInputStream())
+        {
+            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
+            String line = "";
+            while ((line = reader.readLine()) != null)
+            {
+                sb.append(line);
+            }
+        }
+        catch (IOException e)
+        {
+            LOGGER.warn("getBodyString出现问题!");
+        }
+        finally
+        {
+            if (reader != null)
+            {
+                try
+                {
+                    reader.close();
+                }
+                catch (IOException e)
+                {
+                    LOGGER.error(ExceptionUtil.getMessage(e));
+                }
+            }
+        }
+        return sb.toString();
+    }
+}

+ 49 - 0
src/main/java/com/zhongzheng/common/croe/LoginBody.java

@@ -0,0 +1,49 @@
+package com.zhongzheng.common.croe;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+/**
+ * 用户登录对象
+ *
+ * @author zhongzheng
+ */
+@ApiModel("登录用户")
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class LoginBody
+{
+    /**
+     * 用户名
+     */
+    @ApiModelProperty(value ="用户名",required = true)
+    private String username;
+
+    /**
+     * 用户密码
+     */
+    @ApiModelProperty(value ="用户密码",required = true)
+    private String password;
+
+    /**
+     * 验证码
+     */
+    @ApiModelProperty(value ="验证码",required = true)
+    private String code;
+
+    /**
+     * 唯一标识
+     */
+    @ApiModelProperty(value ="验证码uuid",required = true)
+    private String uuid = "";
+
+    @ApiModelProperty(value ="短信验证码")
+    private String smsCode;
+
+    @ApiModelProperty(value ="手机号码")
+    private String phonenumber;
+}

+ 148 - 0
src/main/java/com/zhongzheng/common/croe/LoginUser.java

@@ -0,0 +1,148 @@
+package com.zhongzheng.common.croe;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.zhongzheng.domian.SysUser;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * 登录用户身份权限
+ *
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class LoginUser implements UserDetails
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户唯一标识
+     */
+    private String token;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+    /**
+     * 过期时间
+     */
+    private Long expireTime;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地点
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 权限列表
+     */
+    private Set<String> permissions;
+
+    /**
+     * 用户信息
+     */
+    private SysUser user;
+
+    /**
+     * 手机号隐藏标识 1,不隐藏
+     */
+    private Integer phoneConcealSign;
+
+
+    public LoginUser(SysUser user, Set<String> permissions)
+    {
+        this.user = user;
+        this.permissions = permissions;
+    }
+
+    @JsonIgnore
+    @Override
+    public String getPassword()
+    {
+        return user.getPassword();
+    }
+
+    @Override
+    public String getUsername()
+    {
+        return user.getUserName();
+    }
+
+    /**
+     * 账户是否未过期,过期无法验证
+     */
+    @JsonIgnore
+    @Override
+    public boolean isAccountNonExpired()
+    {
+        return true;
+    }
+
+    /**
+     * 指定用户是否解锁,锁定的用户无法进行身份验证
+     *
+     * @return
+     */
+    @JsonIgnore
+    @Override
+    public boolean isAccountNonLocked()
+    {
+        return true;
+    }
+
+    /**
+     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
+     *
+     * @return
+     */
+    @JsonIgnore
+    @Override
+    public boolean isCredentialsNonExpired()
+    {
+        return true;
+    }
+
+    /**
+     * 是否可用 ,禁用的用户不能身份验证
+     *
+     * @return
+     */
+    @JsonIgnore
+    @Override
+    public boolean isEnabled()
+    {
+        return true;
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities()
+    {
+        return null;
+    }
+}

+ 344 - 0
src/main/java/com/zhongzheng/common/croe/RedisCache.java

@@ -0,0 +1,344 @@
+package com.zhongzheng.common.croe;
+
+import cn.hutool.core.lang.Validator;
+import com.zhongzheng.common.util.ServletUtils;
+import com.zhongzheng.common.util.ToolsUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.data.redis.core.BoundSetOperations;
+import org.springframework.data.redis.core.HashOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * spring redis 工具类
+ *
+ * @author zhongzheng
+ **/
+@SuppressWarnings(value = { "unchecked", "rawtypes" })
+@Component
+public class RedisCache
+{
+    @Autowired
+    public RedisTemplate redisTemplate;
+
+    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key 缓存的键值
+     * @param value 缓存的值
+     */
+    public <T> void setCacheObject(final String key, final T value)
+    {
+        String unionKey = key;
+        if(Validator.isNotEmpty(ServletUtils.getRequest())){
+            String tenantId = ServletUtils.getRequest().getHeader("CompanyId");
+            if(Validator.isNotEmpty(tenantId)){
+                unionKey = tenantId + key;
+            }
+        }
+        redisTemplate.opsForValue().set(unionKey, value);
+    }
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key 缓存的键值
+     * @param value 缓存的值
+     * @param timeout 时间
+     * @param timeUnit 时间颗粒度
+     */
+    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
+    {
+        String unionKey = key;
+        if(Validator.isNotEmpty(ServletUtils.getRequest())){
+            String tenantId = ToolsUtils.getTenantId();
+            if(Validator.isNotEmpty(tenantId)){
+                unionKey = tenantId + key;
+            }
+        }
+        redisTemplate.opsForValue().set(unionKey, value, timeout, timeUnit);
+
+    }
+
+    public <T> void setCacheObjectTenant(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
+    {
+        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
+
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key Redis键
+     * @param timeout 超时时间
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout)
+    {
+        return expire(key, timeout, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key Redis键
+     * @param timeout 超时时间
+     * @param unit 时间单位
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout, final TimeUnit unit)
+    {
+        return redisTemplate.expire(key, timeout, unit);
+    }
+
+    /**
+     * 获得缓存的基本对象。
+     *
+     * @param key 缓存键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> T getCacheObject(final String key)
+    {
+        ValueOperations<String, T> operation = redisTemplate.opsForValue();
+        String unionKey = key;
+        if(Validator.isNotEmpty(ServletUtils.getRequest())){
+            String tenantId = ToolsUtils.getTenantId();
+            if(Validator.isNotEmpty(tenantId)){
+                unionKey = tenantId + key;
+            }
+        }
+        return operation.get(unionKey);
+    }
+
+    /**
+     * 获得缓存的基本对象。
+     *
+     * @param key 缓存键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> T getCacheObjectNoTenant(final String key)
+    {
+        ValueOperations<String, T> operation = redisTemplate.opsForValue();
+        return operation.get(key);
+    }
+
+
+    /**
+     * 删除单个对象
+     *
+     * @param key
+     */
+    public boolean deleteObject(final String key)
+    {
+        String unionKey = key;
+        if(Validator.isNotEmpty(ServletUtils.getRequest())){
+            String tenantId = ServletUtils.getRequest().getHeader("companyid");
+            if(Validator.isNotEmpty(tenantId)){
+                unionKey = tenantId + key;
+            }
+        }
+        return redisTemplate.delete(unionKey);
+    }
+
+    /**
+     * 删除集合对象
+     *
+     * @param collection 多个对象
+     * @return
+     */
+    public long deleteObject(final Collection collection)
+    {
+        return redisTemplate.delete(collection);
+    }
+
+    /**
+     * 缓存List数据
+     *
+     * @param key 缓存的键值
+     * @param dataList 待缓存的List数据
+     * @return 缓存的对象
+     */
+    public <T> long setCacheList(final String key, final List<T> dataList)
+    {
+        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
+        return count == null ? 0 : count;
+    }
+
+    /**
+     * 获得缓存的list对象
+     *
+     * @param key 缓存的键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> List<T> getCacheList(final String key)
+    {
+        return redisTemplate.opsForList().range(key, 0, -1);
+    }
+
+    /**
+     * 缓存Set
+     *
+     * @param key 缓存键值
+     * @param dataSet 缓存的数据
+     * @return 缓存数据的对象
+     */
+    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
+    {
+        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
+        Iterator<T> it = dataSet.iterator();
+        while (it.hasNext())
+        {
+            setOperation.add(it.next());
+        }
+        return setOperation;
+    }
+
+    /**
+     * 获得缓存的set
+     *
+     * @param key
+     * @return
+     */
+    public <T> Set<T> getCacheSet(final String key)
+    {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+    /**
+     * 缓存Map
+     *
+     * @param key
+     * @param dataMap
+     */
+    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
+    {
+        if (dataMap != null) {
+            redisTemplate.opsForHash().putAll(key, dataMap);
+        }
+    }
+
+    /**
+     * 获得缓存的Map
+     *
+     * @param key
+     * @return
+     */
+    public <T> Map<String, T> getCacheMap(final String key)
+    {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * 往Hash中存入数据
+     *
+     * @param key Redis键
+     * @param hKey Hash键
+     * @param value 值
+     */
+    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
+    {
+        redisTemplate.opsForHash().put(key, hKey, value);
+    }
+
+    /**
+     * 获取Hash中的数据
+     *
+     * @param key Redis键
+     * @param hKey Hash键
+     * @return Hash中的对象
+     */
+    public <T> T getCacheMapValue(final String key, final String hKey)
+    {
+        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
+        return opsForHash.get(key, hKey);
+    }
+
+    /**
+     * 获取多个Hash中的数据
+     *
+     * @param key Redis键
+     * @param hKeys Hash键集合
+     * @return Hash对象集合
+     */
+    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
+    {
+        return redisTemplate.opsForHash().multiGet(key, hKeys);
+    }
+
+    /**
+     * 获得缓存的基本对象列表
+     *
+     * @param pattern 字符串前缀
+     * @return 对象列表
+     */
+    public Collection<String> keys(final String pattern)
+    {
+        return redisTemplate.keys(pattern);
+    }
+
+    /**
+     * 加锁,自旋重试三次
+     *
+     * @param redisLockEntity 锁实体
+     * @return
+     */
+    public boolean lock(RedisLockEntity redisLockEntity) {
+        boolean locked = false;
+        int tryCount = 10;
+        while (!locked && tryCount > 0) {
+            locked = redisTemplate.opsForValue().setIfAbsent(redisLockEntity.getLockKey(), redisLockEntity.getRequestId(), 5, TimeUnit.SECONDS);
+            tryCount--;
+            try {
+                Thread.sleep(400);
+            } catch (InterruptedException e) {
+                logger.error("线程被中断" + Thread.currentThread().getId());
+            }
+        }
+        return locked;
+    }
+
+    /**
+     * 非原子解锁,可能解别人锁,不安全
+     *
+     * @param redisLockEntity
+     * @return
+     */
+    public boolean unlock(RedisLockEntity redisLockEntity) {
+        if (redisLockEntity == null || redisLockEntity.getLockKey() == null || redisLockEntity.getRequestId() == null)
+            return false;
+        boolean releaseLock = false;
+        String requestId = (String) redisTemplate.opsForValue().get(redisLockEntity.getLockKey());
+        if (redisLockEntity.getRequestId().equals(requestId)) {
+            releaseLock = redisTemplate.delete(redisLockEntity.getLockKey());
+        }
+        return releaseLock;
+    }
+
+    /**
+     * 使用lua脚本解锁,不会解除别人锁
+     *
+     * @param redisLockEntity
+     * @return
+     */
+    public boolean unlockLua(RedisLockEntity redisLockEntity) {
+        if (redisLockEntity == null || redisLockEntity.getLockKey() == null || redisLockEntity.getRequestId() == null)
+            return false;
+        DefaultRedisScript<Long> redisScript = new DefaultRedisScript();
+        //用于解锁的lua脚本位置
+        redisScript.setLocation(new ClassPathResource("unlock.lua"));
+        redisScript.setResultType(Long.class);
+        //没有指定序列化方式,默认使用上面配置的
+        Object result = redisTemplate.execute(redisScript, Arrays.asList(redisLockEntity.getLockKey()), redisLockEntity.getRequestId());
+        return result.equals(Long.valueOf(1));
+    }
+}

+ 12 - 0
src/main/java/com/zhongzheng/common/croe/RedisLockEntity.java

@@ -0,0 +1,12 @@
+package com.zhongzheng.common.croe;
+
+import lombok.Data;
+
+@Data
+public class RedisLockEntity {
+    public static final String KEY_LOCK_GRADE ="lock_grade";  //锁班级
+
+
+    private String lockKey;
+    private String requestId;
+}

+ 30 - 0
src/main/java/com/zhongzheng/common/croe/UserStatus.java

@@ -0,0 +1,30 @@
+package com.zhongzheng.common.croe;
+
+/**
+ * 用户状态
+ *
+ * @author zhongzheng
+ */
+public enum UserStatus
+{
+    OK("1", "正常"), DISABLE("0", "停用"), DELETED("-1", "删除");
+
+    private final String code;
+    private final String info;
+
+    UserStatus(String code, String info)
+    {
+        this.code = code;
+        this.info = info;
+    }
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public String getInfo()
+    {
+        return info;
+    }
+}

+ 97 - 0
src/main/java/com/zhongzheng/common/filter/BaseException.java

@@ -0,0 +1,97 @@
+package com.zhongzheng.common.filter;
+
+import cn.hutool.core.lang.Validator;
+import com.zhongzheng.common.util.MessageUtils;
+
+/**
+ * 基础异常
+ * 
+ * @author zhongzheng
+ */
+public class BaseException extends RuntimeException
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 所属模块
+     */
+    private String module;
+
+    /**
+     * 错误码
+     */
+    private String code;
+
+    /**
+     * 错误码对应的参数
+     */
+    private Object[] args;
+
+    /**
+     * 错误消息
+     */
+    private String defaultMessage;
+
+    public BaseException(String module, String code, Object[] args, String defaultMessage)
+    {
+        this.module = module;
+        this.code = code;
+        this.args = args;
+        this.defaultMessage = defaultMessage;
+    }
+
+    public BaseException(String module, String code, Object[] args)
+    {
+        this(module, code, args, null);
+    }
+
+    public BaseException(String module, String defaultMessage)
+    {
+        this(module, null, null, defaultMessage);
+    }
+
+    public BaseException(String code, Object[] args)
+    {
+        this(null, code, args, null);
+    }
+
+    public BaseException(String defaultMessage)
+    {
+        this(null, null, null, defaultMessage);
+    }
+
+    @Override
+    public String getMessage()
+    {
+        String message = null;
+        if (!Validator.isEmpty(code))
+        {
+            message = MessageUtils.message(code, args);
+        }
+        if (message == null)
+        {
+            message = defaultMessage;
+        }
+        return message;
+    }
+
+    public String getModule()
+    {
+        return module;
+    }
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public Object[] getArgs()
+    {
+        return args;
+    }
+
+    public String getDefaultMessage()
+    {
+        return defaultMessage;
+    }
+}

+ 16 - 0
src/main/java/com/zhongzheng/common/filter/CaptchaException.java

@@ -0,0 +1,16 @@
+package com.zhongzheng.common.filter;
+
+/**
+ * 验证码错误异常类
+ * 
+ * @author zhongzheng
+ */
+public class CaptchaException extends UserException
+{
+    private static final long serialVersionUID = 1L;
+
+    public CaptchaException()
+    {
+        super("user.jcaptcha.error", null);
+    }
+}

+ 16 - 0
src/main/java/com/zhongzheng/common/filter/CaptchaExpireException.java

@@ -0,0 +1,16 @@
+package com.zhongzheng.common.filter;
+
+/**
+ * 验证码失效异常类
+ * 
+ * @author zhongzheng
+ */
+public class CaptchaExpireException extends UserException
+{
+    private static final long serialVersionUID = 1L;
+
+    public CaptchaExpireException()
+    {
+        super("user.jcaptcha.expire", null);
+    }
+}

+ 167 - 0
src/main/java/com/zhongzheng/common/filter/Constants.java

@@ -0,0 +1,167 @@
+package com.zhongzheng.common.filter;
+
+/**
+ * 通用常量信息
+ *
+ * @author zhongzheng
+ */
+public class Constants
+{
+    /**
+     * UTF-8 字符集
+     */
+    public static final String UTF8 = "UTF-8";
+
+    /**
+     * GBK 字符集
+     */
+    public static final String GBK = "GBK";
+
+    /**
+     * http请求
+     */
+    public static final String HTTP = "http://";
+
+    /**
+     * https请求
+     */
+    public static final String HTTPS = "https://";
+
+    /**
+     * 通用成功标识
+     */
+    public static final String SUCCESS = "0";
+
+    /**
+     * 通用失败标识
+     */
+    public static final String FAIL = "1";
+
+    /**
+     * 登录成功
+     */
+    public static final String LOGIN_SUCCESS = "Success";
+
+    /**
+     * 注销
+     */
+    public static final String LOGOUT = "Logout";
+
+    /**
+     * 登录失败
+     */
+    public static final String LOGIN_FAIL = "Error";
+
+    /**
+     * 验证码 redis key
+     */
+    public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
+
+    /**
+     * 登录用户 redis key
+     */
+    public static final String LOGIN_TOKEN_KEY = "login_tokens:";
+
+    public static final String WX_LOGIN_TOKEN_KEY = "wx_login_tokens:";
+
+
+    /**
+     * 防重提交 redis key
+     */
+    public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";
+
+    /**
+     * 验证码有效期(分钟)
+     */
+    public static final Integer CAPTCHA_EXPIRATION = 2;
+
+    /**
+     * 令牌
+     */
+    public static final String TOKEN = "token";
+
+
+
+    /**
+     * 令牌前缀
+     */
+    public static final String TOKEN_PREFIX = "Bearer ";
+
+    /**
+     * 超级管理员令牌前缀
+     */
+    public static final String SUPER_TOKEN_PREFIX = "Bearer ";
+
+    /**
+     * 微信令牌前缀
+     */
+    public static final String WX_TOKEN_PREFIX = "WX ";
+
+
+    public static final String REGISTER_SMS = "REGISTER-";
+    public static final String FORGET_SMS = "FORGET-";
+    public static final String LOGIN_SMS = "LOGIN-";
+
+    //默认头像
+    public static final String DEFAULT_AVATAR = "oss/images/avatar/20210623/1624414559368_44562477.png";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String LOGIN_USER_KEY = "login_user_key";
+
+    public static final String WX_LOGIN_USER_KEY = "wx_login_user_key";
+
+
+
+
+
+    /**
+     * 用户ID
+     */
+    public static final String JWT_USERID = "userid";
+
+    /**
+     * 用户名称
+     */
+    public static final String JWT_USERNAME = "sub";
+
+    /**
+     * 用户头像
+     */
+    public static final String JWT_AVATAR = "avatar";
+
+    /**
+     * 创建时间
+     */
+    public static final String JWT_CREATED = "created";
+
+    /**
+     * 用户权限
+     */
+    public static final String JWT_AUTHORITIES = "authorities";
+
+    /**
+     * 参数管理 cache key
+     */
+    public static final String SYS_CONFIG_KEY = "sys_config:";
+
+    /**
+     * 字典管理 cache key
+     */
+    public static final String SYS_DICT_KEY = "sys_dict:";
+
+    /**
+     * 字典管理 cache key
+     */
+    public static final String SYS_TOP_DICT_KEY = "sys_top_dict:";
+
+    /**
+     * 资源映射路径 前缀
+     */
+    public static final String RESOURCE_PREFIX = "/profile";
+    /**
+     * 序号
+     */
+    public static final Integer NUMBER = 1;
+}

+ 43 - 0
src/main/java/com/zhongzheng/common/filter/CustomException.java

@@ -0,0 +1,43 @@
+package com.zhongzheng.common.filter;
+
+/**
+ * 自定义异常
+ * 
+ * @author zhongzheng
+ */
+public class CustomException extends RuntimeException
+{
+    private static final long serialVersionUID = 1L;
+
+    private Integer code;
+
+    private String message;
+
+    public CustomException(String message)
+    {
+        this.message = message;
+    }
+
+    public CustomException(String message, Integer code)
+    {
+        this.message = message;
+        this.code = code;
+    }
+
+    public CustomException(String message, Throwable e)
+    {
+        super(message, e);
+        this.message = message;
+    }
+
+    @Override
+    public String getMessage()
+    {
+        return message;
+    }
+
+    public Integer getCode()
+    {
+        return code;
+    }
+}

+ 17 - 0
src/main/java/com/zhongzheng/common/filter/FileException.java

@@ -0,0 +1,17 @@
+package com.zhongzheng.common.filter;
+
+/**
+ * 文件信息异常类
+ * 
+ * @author zhongzheng
+ */
+public class FileException extends BaseException
+{
+    private static final long serialVersionUID = 1L;
+
+    public FileException(String code, Object[] args)
+    {
+        super("file", code, args, null);
+    }
+
+}

+ 16 - 0
src/main/java/com/zhongzheng/common/filter/FileNameLengthLimitExceededException.java

@@ -0,0 +1,16 @@
+package com.zhongzheng.common.filter;
+
+/**
+ * 文件名称超长限制异常类
+ * 
+ * @author zhongzheng
+ */
+public class FileNameLengthLimitExceededException extends FileException
+{
+    private static final long serialVersionUID = 1L;
+
+    public FileNameLengthLimitExceededException(int defaultFileNameLength)
+    {
+        super("upload.filename.exceed.length", new Object[] { defaultFileNameLength });
+    }
+}

+ 16 - 0
src/main/java/com/zhongzheng/common/filter/FileSizeLimitExceededException.java

@@ -0,0 +1,16 @@
+package com.zhongzheng.common.filter;
+
+/**
+ * 文件名大小限制异常类
+ * 
+ * @author zhongzheng
+ */
+public class FileSizeLimitExceededException extends FileException
+{
+    private static final long serialVersionUID = 1L;
+
+    public FileSizeLimitExceededException(long defaultMaxSize)
+    {
+        super("upload.exceed.maxSize", new Object[] { defaultMaxSize });
+    }
+}

+ 72 - 0
src/main/java/com/zhongzheng/common/filter/InvalidExtensionException.java

@@ -0,0 +1,72 @@
+package com.zhongzheng.common.filter;
+
+import org.apache.commons.fileupload.FileUploadException;
+
+import java.util.Arrays;
+
+/**
+ * 文件上传 误异常类
+ * 
+ * @author zhongzheng
+ */
+public class InvalidExtensionException extends FileUploadException
+{
+    private static final long serialVersionUID = 1L;
+
+    private String[] allowedExtension;
+    private String extension;
+    private String filename;
+
+    public InvalidExtensionException(String[] allowedExtension, String extension, String filename)
+    {
+        super("filename : [" + filename + "], extension : [" + extension + "], allowed extension : [" + Arrays.toString(allowedExtension) + "]");
+        this.allowedExtension = allowedExtension;
+        this.extension = extension;
+        this.filename = filename;
+    }
+
+    public String[] getAllowedExtension()
+    {
+        return allowedExtension;
+    }
+
+    public String getExtension()
+    {
+        return extension;
+    }
+
+    public String getFilename()
+    {
+        return filename;
+    }
+
+    public static class InvalidImageExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+
+    public static class InvalidFlashExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+
+    public static class InvalidMediaExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+}

+ 50 - 0
src/main/java/com/zhongzheng/common/filter/JwtAuthenticationTokenFilter.java

@@ -0,0 +1,50 @@
+package com.zhongzheng.common.filter;
+
+import cn.hutool.core.lang.Validator;
+import com.zhongzheng.common.croe.LoginUser;
+import com.zhongzheng.common.model.TokenService;
+import com.zhongzheng.common.util.SecurityUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * token过滤器 验证token有效性
+ *
+ * @author zhongzheng
+ */
+@Component
+public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
+
+    @Autowired
+    private TokenService tokenService;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+            throws ServletException, IOException {
+        try {
+
+            LoginUser loginUser = tokenService.getLoginUser(request);
+
+            if (Validator.isNotNull(loginUser) && Validator.isNull(SecurityUtils.getAuthentication())) {
+                tokenService.verifyToken(loginUser);
+                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
+                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+            }
+        } catch (Exception e) {
+            request.getRequestDispatcher("/filter/token_auth_fail").forward(request, response);
+            return;
+        }
+        chain.doFilter(request, response);
+    }
+}

+ 48 - 0
src/main/java/com/zhongzheng/common/filter/RepeatableFilter.java

@@ -0,0 +1,48 @@
+package com.zhongzheng.common.filter;
+
+import cn.hutool.core.util.StrUtil;
+import org.springframework.http.MediaType;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * Repeatable 过滤器
+ * 
+ * @author zhongzheng
+ */
+public class RepeatableFilter implements Filter
+{
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException
+    {
+        ServletRequest requestWrapper = null;
+        if (request instanceof HttpServletRequest
+                && StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE))
+        {
+            requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
+        }
+        if (null == requestWrapper)
+        {
+            chain.doFilter(request, response);
+        }
+        else
+        {
+            chain.doFilter(requestWrapper, response);
+        }
+    }
+
+    @Override
+    public void destroy()
+    {
+
+    }
+}

+ 78 - 0
src/main/java/com/zhongzheng/common/filter/RepeatedlyRequestWrapper.java

@@ -0,0 +1,78 @@
+package com.zhongzheng.common.filter;
+
+
+
+import com.zhongzheng.common.croe.HttpHelper;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * 构建可重复读取inputStream的request
+ * 
+ * @author zhongzheng
+ */
+public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper
+{
+    private final byte[] body;
+
+    public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException
+    {
+        super(request);
+        request.setCharacterEncoding("UTF-8");
+        response.setCharacterEncoding("UTF-8");
+
+        body = HttpHelper.getBodyString(request).getBytes("UTF-8");
+    }
+
+    @Override
+    public BufferedReader getReader() throws IOException
+    {
+        return new BufferedReader(new InputStreamReader(getInputStream()));
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException
+    {
+        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
+        return new ServletInputStream()
+        {
+            @Override
+            public int read() throws IOException
+            {
+                return bais.read();
+            }
+
+            @Override
+            public int available() throws IOException
+            {
+                return body.length;
+            }
+
+            @Override
+            public boolean isFinished()
+            {
+                return false;
+            }
+
+            @Override
+            public boolean isReady()
+            {
+                return false;
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener)
+            {
+
+            }
+        };
+    }
+}

+ 63 - 0
src/main/java/com/zhongzheng/common/filter/UserConstants.java

@@ -0,0 +1,63 @@
+package com.zhongzheng.common.filter;
+
+/**
+ * 用户常量信息
+ * 
+ * @author zhongzheng
+ */
+public class UserConstants
+{
+    /**
+     * 平台内系统用户的唯一标志
+     */
+    public static final String SYS_USER = "SYS_USER";
+
+    /** 正常状态 */
+    public static final String NORMAL = "0";
+
+    /** 异常状态 */
+    public static final String EXCEPTION = "1";
+
+    /** 用户封禁状态 */
+    public static final String USER_DISABLE = "1";
+
+    /** 角色封禁状态 */
+    public static final String ROLE_DISABLE = "1";
+
+    /** 部门正常状态 */
+    public static final String DEPT_NORMAL = "0";
+
+    /** 部门停用状态 */
+    public static final String DEPT_DISABLE = "1";
+
+    /** 字典正常状态 */
+    public static final String DICT_NORMAL = "0";
+
+    /** 是否为系统默认(是) */
+    public static final String YES = "Y";
+
+    /** 是否菜单外链(是) */
+    public static final String YES_FRAME = "0";
+
+    /** 是否菜单外链(否) */
+    public static final String NO_FRAME = "1";
+
+    /** 菜单类型(目录) */
+    public static final String TYPE_DIR = "M";
+
+    /** 菜单类型(菜单) */
+    public static final String TYPE_MENU = "C";
+
+    /** 菜单类型(按钮) */
+    public static final String TYPE_BUTTON = "F";
+
+    /** Layout组件标识 */
+    public final static String LAYOUT = "Layout";
+
+    /** ParentView组件标识 */
+    public final static String PARENT_VIEW = "ParentView";
+
+    /** 校验返回结果码 */
+    public final static String UNIQUE = "0";
+    public final static String NOT_UNIQUE = "1";
+}

+ 16 - 0
src/main/java/com/zhongzheng/common/filter/UserException.java

@@ -0,0 +1,16 @@
+package com.zhongzheng.common.filter;
+
+/**
+ * 用户信息异常类
+ * 
+ * @author zhongzheng
+ */
+public class UserException extends BaseException
+{
+    private static final long serialVersionUID = 1L;
+
+    public UserException(String code, Object[] args)
+    {
+        super("user", code, args, null);
+    }
+}

+ 17 - 0
src/main/java/com/zhongzheng/common/filter/UserPasswordNotMatchException.java

@@ -0,0 +1,17 @@
+package com.zhongzheng.common.filter;
+
+
+/**
+ * 用户密码不正确或不符合规范异常类
+ * 
+ * @author zhongzheng
+ */
+public class UserPasswordNotMatchException extends UserException
+{
+    private static final long serialVersionUID = 1L;
+
+    public UserPasswordNotMatchException()
+    {
+        super("user.password.not.match", null);
+    }
+}

+ 93 - 0
src/main/java/com/zhongzheng/common/filter/XssFilter.java

@@ -0,0 +1,93 @@
+package com.zhongzheng.common.filter;
+
+import cn.hutool.core.util.StrUtil;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 防止XSS攻击的过滤器
+ * 
+ * @author zhongzheng
+ */
+public class XssFilter implements Filter
+{
+    /**
+     * 排除链接
+     */
+    public List<String> excludes = new ArrayList<>();
+
+    /**
+     * xss过滤开关
+     */
+    public boolean enabled = false;
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        String tempExcludes = filterConfig.getInitParameter("excludes");
+        String tempEnabled = filterConfig.getInitParameter("enabled");
+        if (StrUtil.isNotEmpty(tempExcludes))
+        {
+            String[] url = tempExcludes.split(",");
+            for (int i = 0; url != null && i < url.length; i++)
+            {
+                excludes.add(url[i]);
+            }
+        }
+        if (StrUtil.isNotEmpty(tempEnabled))
+        {
+            enabled = Boolean.valueOf(tempEnabled);
+        }
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException
+    {
+        HttpServletRequest req = (HttpServletRequest) request;
+        HttpServletResponse resp = (HttpServletResponse) response;
+        if (handleExcludeURL(req, resp))
+        {
+            chain.doFilter(request, response);
+            return;
+        }
+        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
+        chain.doFilter(xssRequest, response);
+    }
+
+    private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response)
+    {
+        if (!enabled)
+        {
+            return true;
+        }
+        if (excludes == null || excludes.isEmpty())
+        {
+            return false;
+        }
+        String url = request.getServletPath();
+        for (String pattern : excludes)
+        {
+            Pattern p = Pattern.compile("^" + pattern);
+            Matcher m = p.matcher(url);
+            if (m.find())
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void destroy()
+    {
+
+    }
+}

+ 105 - 0
src/main/java/com/zhongzheng/common/filter/XssHttpServletRequestWrapper.java

@@ -0,0 +1,105 @@
+package com.zhongzheng.common.filter;
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.http.HtmlUtil;
+import org.apache.commons.io.IOUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+/**
+ * XSS过滤处理
+ * 
+ * @author zhongzheng
+ */
+public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper
+{
+    /**
+     * @param request
+     */
+    public XssHttpServletRequestWrapper(HttpServletRequest request)
+    {
+        super(request);
+    }
+
+    @Override
+    public String[] getParameterValues(String name)
+    {
+        String[] values = super.getParameterValues(name);
+        if (values != null)
+        {
+            int length = values.length;
+            String[] escapseValues = new String[length];
+            for (int i = 0; i < length; i++)
+            {
+                // 防xss攻击和过滤前后空格
+                escapseValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim();
+            }
+            return escapseValues;
+        }
+        return super.getParameterValues(name);
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException
+    {
+        // 非json类型,直接返回
+        if (!isJsonRequest())
+        {
+            return super.getInputStream();
+        }
+
+        // 为空,直接返回
+        String json = IOUtils.toString(super.getInputStream(), "utf-8");
+        if (Validator.isEmpty(json))
+        {
+            return super.getInputStream();
+        }
+
+        // xss过滤
+        json = HtmlUtil.cleanHtmlTag(json).trim();
+        final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes("utf-8"));
+        return new ServletInputStream()
+        {
+            @Override
+            public boolean isFinished()
+            {
+                return true;
+            }
+
+            @Override
+            public boolean isReady()
+            {
+                return true;
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener)
+            {
+            }
+
+            @Override
+            public int read() throws IOException
+            {
+                return bis.read();
+            }
+        };
+    }
+
+    /**
+     * 是否是Json请求
+     * 
+     * @param request
+     */
+    public boolean isJsonRequest()
+    {
+        String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
+        return MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(header);
+    }
+}

+ 56 - 0
src/main/java/com/zhongzheng/common/interceptor/RepeatSubmitInterceptor.java

@@ -0,0 +1,56 @@
+package com.zhongzheng.common.interceptor;
+
+import com.alibaba.fastjson.JSONObject;
+import com.zhongzheng.common.annotation.RepeatSubmit;
+import com.zhongzheng.common.model.AjaxResult;
+import com.zhongzheng.common.util.ServletUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+
+/**
+ * 防止重复提交拦截器
+ *
+ * @author zhongzheng
+ */
+@Component
+public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter
+{
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
+    {
+        if (handler instanceof HandlerMethod)
+        {
+            HandlerMethod handlerMethod = (HandlerMethod) handler;
+            Method method = handlerMethod.getMethod();
+            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
+            if (annotation != null)
+            {
+                if (this.isRepeatSubmit(request))
+                {
+                    AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
+                    ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));
+                    return false;
+                }
+            }
+            return true;
+        }
+        else
+        {
+            return super.preHandle(request, response, handler);
+        }
+    }
+
+    /**
+     * 验证是否重复提交由子类实现具体的防重复提交的规则
+     *
+     * @param request
+     * @return
+     * @throws Exception
+     */
+    public abstract boolean isRepeatSubmit(HttpServletRequest request);
+}

+ 126 - 0
src/main/java/com/zhongzheng/common/interceptor/impl/SameUrlDataInterceptor.java

@@ -0,0 +1,126 @@
+package com.zhongzheng.common.interceptor.impl;
+
+import cn.hutool.core.lang.Validator;
+import com.alibaba.fastjson.JSONObject;
+import com.zhongzheng.common.croe.HttpHelper;
+import com.zhongzheng.common.croe.RedisCache;
+import com.zhongzheng.common.filter.Constants;
+import com.zhongzheng.common.filter.RepeatedlyRequestWrapper;
+import com.zhongzheng.common.interceptor.RepeatSubmitInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 判断请求url和数据是否和上一次相同,
+ * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
+ * 
+ * @author zhongzheng
+ */
+@Component
+public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
+{
+    public final String REPEAT_PARAMS = "repeatParams";
+
+    public final String REPEAT_TIME = "repeatTime";
+
+    // 令牌自定义标识
+    @Value("${token.header}")
+    private String header;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 间隔时间,单位:秒 默认10秒
+     * 
+     * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
+     */
+    private int intervalTime = 10;
+
+    public void setIntervalTime(int intervalTime)
+    {
+        this.intervalTime = intervalTime;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public boolean isRepeatSubmit(HttpServletRequest request)
+    {
+        String nowParams = "";
+        if (request instanceof RepeatedlyRequestWrapper)
+        {
+            RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
+            nowParams = HttpHelper.getBodyString(repeatedlyRequest);
+        }
+
+        // body参数为空,获取Parameter的数据
+        if (Validator.isEmpty(nowParams))
+        {
+            nowParams = JSONObject.toJSONString(request.getParameterMap());
+        }
+        Map<String, Object> nowDataMap = new HashMap<String, Object>();
+        nowDataMap.put(REPEAT_PARAMS, nowParams);
+        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
+
+        // 请求地址(作为存放cache的key值)
+        String url = request.getRequestURI();
+
+        // 唯一值(没有消息头则使用请求地址)
+        String submitKey = request.getHeader(header);
+        if (Validator.isEmpty(submitKey))
+        {
+            submitKey = url;
+        }
+
+        // 唯一标识(指定key + 消息头)
+        String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey;
+
+        Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
+        if (sessionObj != null)
+        {
+            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
+            if (sessionMap.containsKey(url))
+            {
+                Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
+                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap))
+                {
+                    return true;
+                }
+            }
+        }
+        Map<String, Object> cacheMap = new HashMap<String, Object>();
+        cacheMap.put(url, nowDataMap);
+        redisCache.setCacheObject(cacheRepeatKey, cacheMap, intervalTime, TimeUnit.SECONDS);
+        return false;
+    }
+
+    /**
+     * 判断参数是否相同
+     */
+    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)
+    {
+        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
+        String preParams = (String) preMap.get(REPEAT_PARAMS);
+        return nowParams.equals(preParams);
+    }
+
+    /**
+     * 判断两次间隔时间
+     */
+    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap)
+    {
+        long time1 = (Long) nowMap.get(REPEAT_TIME);
+        long time2 = (Long) preMap.get(REPEAT_TIME);
+        if ((time1 - time2) < (this.intervalTime * 1000))
+        {
+            return true;
+        }
+        return false;
+    }
+}

+ 192 - 0
src/main/java/com/zhongzheng/common/model/AjaxResult.java

@@ -0,0 +1,192 @@
+package com.zhongzheng.common.model;
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.http.HttpStatus;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 操作消息提醒
+ *
+ * @author zhongzheng
+ */
+public class AjaxResult<T> extends HashMap<String, Object>
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 状态码 */
+    public static final String CODE_TAG = "code";
+
+    /** 返回内容 */
+    public static final String MSG_TAG = "msg";
+
+    /** 数据对象 */
+    public static final String DATA_TAG = "data";
+
+    public Integer getCode(){
+        return (Integer) super.get(CODE_TAG);
+    }
+
+    public String getMsg(){
+        return (String) super.get(MSG_TAG);
+    }
+    public T getData(){
+        return (T) super.get(DATA_TAG);
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
+     */
+    public AjaxResult()
+    {
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象
+     *
+     * @param code 状态码
+     * @param msg 返回内容
+     */
+    public AjaxResult(int code, String msg)
+    {
+        super.put(CODE_TAG, code);
+        super.put(MSG_TAG, msg);
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象
+     *
+     * @param code 状态码
+     * @param msg 返回内容
+     * @param data 数据对象
+     */
+    public AjaxResult(int code, String msg, T data)
+    {
+        super.put(CODE_TAG, code);
+        super.put(MSG_TAG, msg);
+        if (Validator.isNotNull(data))
+        {
+            super.put(DATA_TAG, data);
+        }
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @return 成功消息
+     */
+    public static AjaxResult<Void> success()
+    {
+        return AjaxResult.success("操作成功");
+    }
+
+    /**
+     * 返回成功拦截日志消息
+     *
+     * @return 成功拦截日志消息
+     */
+    public static AjaxResult<Void> success_log(Long primary_key_id,String oper_content)
+    {
+        Map<String,Object> logMap = new HashMap<>();
+        logMap.put("primary_key_id", primary_key_id);
+        logMap.put("oper_content", oper_content);
+        AjaxResult ajax = AjaxResult.success();
+        ajax.put("log", logMap);
+        return ajax;
+    }
+
+    /**
+     * 返回成功数据
+     *
+     * @return 成功消息
+     */
+    public static <T> AjaxResult<T> success(T data)
+    {
+        return AjaxResult.success("操作成功", data);
+    }
+
+    /**
+     * 返回成功数据
+     *
+     * @return 成功消息
+     */
+    public static <T> AjaxResult<T> successNoMsg(T data)
+    {
+        return AjaxResult.success("", data);
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @param msg 返回内容
+     * @return 成功消息
+     */
+    public static AjaxResult<Void> success(String msg)
+    {
+        return AjaxResult.success(msg, null);
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @param msg 返回内容
+     * @param data 数据对象
+     * @return 成功消息
+     */
+    public static <T> AjaxResult<T> success(String msg, T data)
+    {
+        return new AjaxResult(HttpStatus.HTTP_OK, msg, data);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @return
+     */
+    public static AjaxResult<Void> error()
+    {
+        return AjaxResult.error("操作失败");
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param msg 返回内容
+     * @return 警告消息
+     */
+    public static AjaxResult<Void> error(String msg)
+    {
+        return AjaxResult.error(msg, null);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param msg 返回内容
+     * @param data 数据对象
+     * @return 警告消息
+     */
+    public static <T> AjaxResult<T> error(String msg, T data)
+    {
+        return new AjaxResult(HttpStatus.HTTP_INTERNAL_ERROR, msg, data);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param code 状态码
+     * @param msg 返回内容
+     * @return 警告消息
+     */
+    public static AjaxResult<Void> error(int code, String msg)
+    {
+        return new AjaxResult(code, msg, null);
+    }
+
+
+    public static <T> AjaxResult<T> error(int code, String msg, T data)
+    {
+        return new AjaxResult(code, msg, data);
+    }
+}

+ 34 - 0
src/main/java/com/zhongzheng/common/model/AuthenticationEntryPointImpl.java

@@ -0,0 +1,34 @@
+package com.zhongzheng.common.model;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpStatus;
+import com.alibaba.fastjson.JSON;
+import com.zhongzheng.common.util.ServletUtils;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * 认证失败处理类 返回未授权
+ *
+ * @author zhongzheng
+ */
+@Component
+public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
+{
+    private static final long serialVersionUID = -8970718410437077606L;
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
+            throws IOException
+    {
+        int code = HttpStatus.HTTP_UNAUTHORIZED;
+        String msg = StrUtil.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
+        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
+    }
+}

+ 49 - 0
src/main/java/com/zhongzheng/common/model/BaseEntity.java

@@ -0,0 +1,49 @@
+package com.zhongzheng.common.model;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Entity基类
+ * 
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class BaseEntity implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 搜索值 */
+    private String searchValue;
+
+    /** 创建者 */
+    private String createBy;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新者 */
+    private String updateBy;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /** 备注 */
+    private String remark;
+
+    /** 请求参数 */
+    private Map<String, Object> params = new HashMap<>();
+
+}

+ 52 - 0
src/main/java/com/zhongzheng/common/model/LogoutSuccessHandlerImpl.java

@@ -0,0 +1,52 @@
+package com.zhongzheng.common.model;
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.http.HttpStatus;
+import com.alibaba.fastjson.JSON;
+import com.zhongzheng.common.croe.AsyncFactory;
+import com.zhongzheng.common.croe.AsyncManager;
+import com.zhongzheng.common.croe.LoginUser;
+import com.zhongzheng.common.filter.Constants;
+import com.zhongzheng.common.util.ServletUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 自定义退出处理类 返回成功
+ * 
+ * @author zhongzheng
+ */
+@Configuration
+public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
+{
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 退出处理
+     * 
+     * @return
+     */
+    @Override
+    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
+            throws IOException, ServletException
+    {
+            LoginUser loginUser = tokenService.getLoginUser(request);
+            if (Validator.isNotNull(loginUser))
+            {
+                String userName = loginUser.getUsername();
+                // 删除用户缓存记录
+                tokenService.delLoginUser(loginUser.getToken());
+                // 记录用户退出日志
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功"));
+            }
+        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.HTTP_OK, "退出成功")));
+    }
+}

+ 21 - 0
src/main/java/com/zhongzheng/common/model/ResultBean.java

@@ -0,0 +1,21 @@
+package com.zhongzheng.common.model;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+
+@Data
+@ApiModel("【oss】视图对象")
+public class ResultBean {
+
+
+    private Object resultContent;
+
+
+
+    public ResultBean(Object resultContent){
+        this.resultContent = resultContent;
+    }
+
+
+
+}

+ 150 - 0
src/main/java/com/zhongzheng/common/model/SysLoginService.java

@@ -0,0 +1,150 @@
+package com.zhongzheng.common.model;
+
+import cn.hutool.core.lang.Validator;
+import com.zhongzheng.bo.UserMarketingLoginBo;
+import com.zhongzheng.common.croe.AsyncFactory;
+import com.zhongzheng.common.croe.AsyncManager;
+import com.zhongzheng.common.croe.LoginUser;
+import com.zhongzheng.common.croe.RedisCache;
+import com.zhongzheng.common.filter.*;
+import com.zhongzheng.common.util.AES;
+import com.zhongzheng.common.util.DateUtils;
+import com.zhongzheng.common.util.MessageUtils;
+import com.zhongzheng.domian.SysUser;
+import com.zhongzheng.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.io.InputStream;
+
+/**
+ * 登录校验方法
+ *
+ * @author zhongzheng
+ */
+@Component
+public class SysLoginService
+{
+    @Autowired
+    private TokenService tokenService;
+    @Resource
+    private AuthenticationManager authenticationManager;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private ISysUserService iSysUserService;
+
+
+    /**
+     * 登录验证
+     *
+     * @param username 用户名
+     * @param password 密码
+     * @param code 验证码
+     * @param uuid 唯一标识
+     * @return 结果
+     */
+    public String login(String username, String password, String code, String uuid,String smsCode,String tel)
+    {
+        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
+        String captcha = redisCache.getCacheObject(verifyKey);
+        redisCache.deleteObject(verifyKey);
+        if (captcha == null)
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha))
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
+            throw new CaptchaException();
+        }
+        // 用户验证
+        Authentication authentication = null;
+        try
+        {
+            String rsaPrivate = null;
+           try {
+                InputStream certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("config/pri.key");
+                rsaPrivate = AES.getStringByInputStream_1(certStream);
+                certStream.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+
+            }
+            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
+            if(password.length()>20){
+                password = AES.decrypt(password,rsaPrivate);
+            }
+            authentication = authenticationManager
+                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
+        }
+        catch (Exception e)
+        {
+            if (e instanceof BadCredentialsException)
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                throw new UserPasswordNotMatchException();
+            }
+            else
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
+                throw new CustomException(e.getMessage());
+            }
+        }
+        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
+        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        SysUser user = iSysUserService.updateLoginTimeIp(loginUser.getUser().getUserId());
+        loginUser.setUser(user);
+        // 生成token
+        return tokenService.createToken(loginUser);
+    }
+
+    public String businessLogin(UserMarketingLoginBo loginBody)
+    {
+        String password;
+        if(Validator.isEmpty(loginBody.getPwd())){
+            throw new CustomException("密码不能为空");
+        }
+        try
+        {
+        String rsaPrivate = null;
+        try {
+            InputStream certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("config/pri.key");
+            rsaPrivate = AES.getStringByInputStream_1(certStream);
+            certStream.close();
+        } catch (Exception e) {
+            throw new CustomException("参数错误"+e.getMessage());
+        }
+            password = AES.decrypt(loginBody.getPwd(),rsaPrivate);
+            String[] param = password.split("#");
+            if(param.length!=2||!"test".equals(param[0])){
+                throw new CustomException("参数内容非法");
+            }
+            Long nowTime = DateUtils.getNowTime();
+            Long paramTime = Long.valueOf(param[1]);
+            if(Validator.isEmpty(paramTime)){
+                throw new CustomException("参数内容错误");
+            }
+        }
+        catch (Exception e)
+        {
+            throw new CustomException(e.getMessage());
+        }
+        String username = "admin";
+        LoginUser loginUser = new LoginUser();
+        //普通系统用户
+        SysUser user = iSysUserService.selectUserByUserName(username);
+        user = iSysUserService.updateLoginTimeIp(user.getUserId());
+        loginUser.setUser(user);
+        // 生成token
+        return tokenService.createToken(loginUser);
+    }
+}

+ 80 - 0
src/main/java/com/zhongzheng/common/model/SysLogininfor.java

@@ -0,0 +1,80 @@
+package com.zhongzheng.common.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 系统访问记录表 sys_logininfor
+ *
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class SysLogininfor implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * ID
+     */
+    @TableId(value = "info_id", type = IdType.AUTO)
+    private Long infoId;
+
+    /**
+     * 用户账号
+     */
+    private String userName;
+
+    /**
+     * 登录状态 0成功 1失败
+     */
+    private String status;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地点
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 提示消息
+     */
+    private String msg;
+
+
+    /**
+     * 访问时间
+     */
+    private Date loginTime;
+
+    /**
+     * 请求参数
+     */
+    @TableField(exist = false)
+    private Map<String, Object> params = new HashMap<>();
+
+}

+ 66 - 0
src/main/java/com/zhongzheng/common/model/SysPermissionService.java

@@ -0,0 +1,66 @@
+package com.zhongzheng.common.model;
+
+import com.zhongzheng.domian.SysUser;
+import com.zhongzheng.service.ISysMenuService;
+import com.zhongzheng.service.ISysRoleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * 用户权限处理
+ *
+ * @author zhongzheng
+ */
+@Component
+public class SysPermissionService
+{
+    @Autowired
+    private ISysRoleService roleService;
+
+    @Autowired
+    private ISysMenuService iBsSysMenuService;
+    /**
+     * 获取角色数据权限
+     *
+     * @param user 用户信息
+     * @return 角色权限信息
+     */
+    public Set<String> getRolePermission(SysUser user)
+    {
+        Set<String> roles = new HashSet<String>();
+        // 管理员拥有所有权限
+        if (user.isAdmin())
+        {
+            roles.add("admin");
+        }
+        else
+        {
+            roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
+        }
+        return roles;
+    }
+
+    /**
+     * 企业b端获取菜单数据权限
+     *
+     * @param user 用户信息
+     * @return 菜单权限信息
+     */
+    public Set<String> getMenuPermission(SysUser user)
+    {
+        Set<String> perms = new HashSet<String>();
+        // 管理员拥有所有权限
+        if (user.isAdmin())
+        {
+            perms.add("*:*:*");
+        }
+        else
+        {
+            perms.addAll(iBsSysMenuService.selectMenuPermsByUserId(user.getUserId()));
+        }
+        return perms;
+    }
+}

+ 47 - 0
src/main/java/com/zhongzheng/common/model/TableDataInfo.java

@@ -0,0 +1,47 @@
+package com.zhongzheng.common.model;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 表格分页数据对象
+ * 
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class TableDataInfo<T> implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 总记录数 */
+    private long total;
+
+    /** 列表数据 */
+    private List<T> rows;
+
+    /** 消息状态码 */
+    private int code;
+
+    /** 消息内容 */
+    private String msg;
+
+    /**
+     * 分页
+     * 
+     * @param list 列表数据
+     * @param total 总记录数
+     */
+    public TableDataInfo(List<T> list, int total)
+    {
+        this.rows = list;
+        this.total = total;
+    }
+
+}

+ 272 - 0
src/main/java/com/zhongzheng/common/model/TokenService.java

@@ -0,0 +1,272 @@
+package com.zhongzheng.common.model;
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.http.useragent.UserAgent;
+import cn.hutool.http.useragent.UserAgentUtil;
+import com.zhongzheng.common.croe.AsyncFactory;
+import com.zhongzheng.common.croe.AsyncManager;
+import com.zhongzheng.common.croe.LoginUser;
+import com.zhongzheng.common.croe.RedisCache;
+import com.zhongzheng.common.filter.Constants;
+import com.zhongzheng.common.filter.CustomException;
+import com.zhongzheng.common.filter.UserPasswordNotMatchException;
+import com.zhongzheng.common.util.AddressUtils;
+import com.zhongzheng.common.util.IpUtils;
+import com.zhongzheng.common.util.MessageUtils;
+import com.zhongzheng.common.util.ServletUtils;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * token验证处理
+ *
+ * @author zhongzheng
+ */
+@Component
+public class TokenService
+{
+    // 令牌自定义标识
+    @Value("${token.header}")
+    private String header;
+
+    // 令牌秘钥
+    @Value("${token.secret}")
+    private String secret;
+
+    // 令牌有效期(默认30分钟)
+    @Value("${token.expireTime}")
+    private int expireTime;
+
+    protected static final long MILLIS_SECOND = 1000;
+
+    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
+
+    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Resource
+    private AuthenticationManager authenticationManager;
+
+    /**
+     * 获取用户身份信息
+     *
+     * @return 用户信息
+     */
+    public LoginUser getLoginUser(HttpServletRequest request)  {
+        //测试用户
+        String test_token = request.getHeader("X-Auth-Token");
+        if("test".equals(test_token)){
+            return getTestUser();
+        }
+        // 获取请求携带的令牌
+        String token = getToken(request);
+        if (Validator.isNotEmpty(token))
+        {
+            String uuid = "";
+            Claims claims = parseToken(token);
+            // 解析对应的权限以及用户信息
+            uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
+            String userKey = getTokenKey(uuid);
+            LoginUser user = redisCache.getCacheObject(userKey);
+            return user;
+        }
+
+        return null;
+    }
+
+    private LoginUser getTestUser(){
+        Authentication authentication = null;
+        try
+        {
+            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
+            authentication = authenticationManager
+                    .authenticate(new UsernamePasswordAuthenticationToken("admin", "admin159"));
+        }
+        catch (Exception e)
+        {
+            if (e instanceof BadCredentialsException)
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor("admin", Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                throw new UserPasswordNotMatchException();
+            }
+            else
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor("admin", Constants.LOGIN_FAIL, e.getMessage()));
+                throw new CustomException(e.getMessage());
+            }
+        }
+        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        loginUser.setExpireTime(System.currentTimeMillis());
+        return loginUser;
+    }
+
+    /**
+     * 设置用户身份信息
+     */
+    public void setLoginUser(LoginUser loginUser)
+    {
+        if (Validator.isNotNull(loginUser) && Validator.isNotEmpty(loginUser.getToken()))
+        {
+            refreshToken(loginUser);
+        }
+    }
+
+    /**
+     * 删除用户身份信息
+     */
+    public void delLoginUser(String token)
+    {
+        if (Validator.isNotEmpty(token))
+        {
+            String userKey = getTokenKey(token);
+            redisCache.deleteObject(userKey);
+        }
+    }
+
+    /**
+     * 创建令牌
+     *
+     * @param loginUser 用户信息
+     * @return 令牌
+     */
+    public String createToken(LoginUser loginUser)
+    {
+        String token = IdUtil.fastUUID();
+        loginUser.setToken(token);
+        setUserAgent(loginUser);
+        refreshToken(loginUser);
+
+        Map<String, Object> claims = new HashMap<>();
+        claims.put(Constants.LOGIN_USER_KEY, token);
+        return createToken(claims);
+    }
+
+
+    /**
+     * 验证令牌有效期,相差不足20分钟,自动刷新缓存
+     *
+     * @param loginUser
+     * @return 令牌
+     */
+    public void verifyToken(LoginUser loginUser)
+    {
+        long expireTime = loginUser.getExpireTime();
+        long currentTime = System.currentTimeMillis();
+        if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
+        {
+            refreshToken(loginUser);
+        }
+    }
+
+    /**
+     * 刷新令牌有效期
+     *
+     * @param loginUser 登录信息
+     */
+    public void refreshToken(LoginUser loginUser)
+    {
+        loginUser.setLoginTime(System.currentTimeMillis());
+        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
+        // 根据uuid将loginUser缓存
+        String userKey = getTokenKey(loginUser.getToken());
+        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
+    }
+
+
+    /**
+     * 设置用户代理信息
+     *
+     * @param loginUser 登录信息
+     */
+    public void setUserAgent(LoginUser loginUser)
+    {
+        UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
+        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+        loginUser.setIpaddr(ip);
+        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
+        loginUser.setBrowser(userAgent.getBrowser().getName());
+        loginUser.setOs(userAgent.getOs().getName());
+    }
+
+
+    /**
+     * 从数据声明生成令牌
+     *
+     * @param claims 数据声明
+     * @return 令牌
+     */
+    private String createToken(Map<String, Object> claims)
+    {
+        String token = Jwts.builder()
+                .setClaims(claims)
+                .signWith(SignatureAlgorithm.HS512, secret).compact();
+        return token;
+    }
+
+    /**
+     * 从令牌中获取数据声明
+     *
+     * @param token 令牌
+     * @return 数据声明
+     */
+    private Claims parseToken(String token)
+    {
+        Claims claims;
+        claims = Jwts.parser()
+                .setSigningKey(secret) // 设置标识名
+                .parseClaimsJws(token)  //解析token
+                .getBody();
+        return claims;
+    }
+
+    /**
+     * 从令牌中获取用户名
+     *
+     * @param token 令牌
+     * @return 用户名
+     */
+    public String getUsernameFromToken(String token)
+    {
+        Claims claims = parseToken(token);
+        return claims.getSubject();
+    }
+
+
+    /**
+     * 获取请求token
+     *
+     * @param request
+     * @return token
+     */
+    private String getToken(HttpServletRequest request)
+    {
+        String token = request.getHeader(header);
+        if (Validator.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
+        {
+            token = token.replace(Constants.TOKEN_PREFIX, "");
+        }
+        return token;
+    }
+
+    private String getTokenKey(String uuid)
+    {
+        return Constants.LOGIN_TOKEN_KEY + uuid;
+    }
+}

+ 64 - 0
src/main/java/com/zhongzheng/common/model/UserDetailsServiceImpl.java

@@ -0,0 +1,64 @@
+package com.zhongzheng.common.model;
+
+import cn.hutool.core.lang.Validator;
+import com.zhongzheng.common.croe.LoginUser;
+import com.zhongzheng.common.croe.UserStatus;
+import com.zhongzheng.common.filter.BaseException;
+import com.zhongzheng.domian.SysUser;
+import com.zhongzheng.service.ISysUserService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+/**
+ * 用户验证处理
+ *
+ * @author zhongzheng
+ */
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService
+{
+    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
+
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private SysPermissionService permissionService;
+
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
+    {
+        //普通系统用户
+        SysUser user = userService.selectUserByUserName(username);
+        if (Validator.isNull(user))
+        {
+            log.info("登录用户:{} 不存在.", username);
+            throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
+        }
+        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
+        {
+            log.info("登录用户:{} 已被删除.", username);
+            throw new BaseException("对不起,您的账号:" + username + " 已被删除");
+        }
+        else if (user.getStatus().longValue()==0L)
+        {
+            log.info("登录用户:{} 已被停用.", username);
+            throw new BaseException("对不起,您的账号:" + username + " 已停用");
+        }
+
+        return createLoginUser(user);
+    }
+
+    public UserDetails createLoginUser(SysUser user)
+    {
+        return new LoginUser(user, permissionService.getMenuPermission(user));
+    }
+
+
+}

+ 11 - 0
src/main/java/com/zhongzheng/common/schedule/IScheduleService.java

@@ -0,0 +1,11 @@
+package com.zhongzheng.common.schedule;
+
+/**
+ * 保利威视频信息Service接口
+ *
+ * @author ruoyi
+ * @date 2021-06-11
+ */
+public interface IScheduleService {
+
+}

+ 17 - 0
src/main/java/com/zhongzheng/common/schedule/impl/ScheduleServiceImpl.java

@@ -0,0 +1,17 @@
+package com.zhongzheng.common.schedule.impl;
+
+import com.zhongzheng.common.schedule.IScheduleService;
+import org.springframework.stereotype.Service;
+
+
+/**
+ * 定时任务
+ *
+ * @author change
+ * @date 2021-06-11
+ */
+@Service
+public class ScheduleServiceImpl implements IScheduleService {
+
+
+}

+ 159 - 0
src/main/java/com/zhongzheng/common/util/AES.java

@@ -0,0 +1,159 @@
+package com.zhongzheng.common.util;
+
+import org.apache.commons.codec.binary.Base64;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.security.*;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+
+public class AES {
+    public static boolean initialized = false;
+
+    /**
+     * AES解密
+     * @param content 密文
+     * @return
+     * @throws InvalidAlgorithmParameterException
+     * @throws NoSuchProviderException
+     */
+    public static byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {
+        initialize();
+        try {
+//            Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
+            Key sKeySpec = new SecretKeySpec(keyByte, "AES");
+
+            cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化
+            byte[] result = cipher.doFinal(content);
+            return result;
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        } catch (NoSuchPaddingException e) {
+            e.printStackTrace();
+        } catch (InvalidKeyException e) {
+            e.printStackTrace();
+        } catch (IllegalBlockSizeException e) {
+            e.printStackTrace();
+        } catch (BadPaddingException e) {
+            e.printStackTrace();
+        } catch (NoSuchProviderException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static void initialize(){
+        if (initialized) return;
+        Security.addProvider(new BouncyCastleProvider());
+        initialized = true;
+    }
+    //生成iv
+    public static AlgorithmParameters generateIV(byte[] iv) throws Exception{
+        AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
+        params.init(new IvParameterSpec(iv));
+        return params;
+    }
+
+    public static String SHA1(String decript) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-1");
+            digest.update(decript.getBytes());
+            byte messageDigest[] = digest.digest();
+            // Create Hex String
+            StringBuffer hexString = new StringBuffer();
+            // 字节数组转换为 十六进制 数
+            for (int i = 0; i < messageDigest.length; i++) {
+                String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
+                if (shaHex.length() < 2) {
+                    hexString.append(0);
+                }
+                hexString.append(shaHex);
+            }
+            return hexString.toString();
+
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    /**
+     * RSA公钥加密
+     *
+     * @param str
+     *            加密字符串
+     * @param publicKey
+     *            公钥
+     * @return 密文
+     * @throws Exception
+     *             加密过程中的异常信息
+     */
+    public static String encrypt( String str, String publicKey ) throws Exception{
+        //base64编码的公钥
+        byte[] decoded = Base64.decodeBase64(publicKey);
+        RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
+        //RSA加密
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
+        String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));
+        return outStr;
+    }
+
+    /**
+     * RSA私钥解密
+     *
+     * @param str
+     *            加密字符串
+     * @param privateKey
+     *            私钥
+     * @return 铭文
+     * @throws Exception
+     *             解密过程中的异常信息
+     */
+    public static String decrypt(String str, String privateKey) throws Exception{
+        //64位解码加密后的字符串
+        byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
+        //base64编码的私钥
+        byte[] decoded = Base64.decodeBase64(privateKey);
+        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
+        //RSA解密
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.DECRYPT_MODE, priKey);
+        String outStr = new String(cipher.doFinal(inputByte));
+        return outStr;
+    }
+
+    public static String getStringByInputStream_1(InputStream inputStream){
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        try {
+            byte[] b = new byte[10240];
+            int n;
+            while ((n = inputStream.read(b)) != -1) {
+                outputStream.write(b, 0, n);
+            }
+        } catch (Exception e) {
+            try {
+                inputStream.close();
+                outputStream.close();
+            } catch (Exception e1) {
+            }
+        }
+        return outputStream.toString();
+    }
+}

+ 55 - 0
src/main/java/com/zhongzheng/common/util/AddressUtils.java

@@ -0,0 +1,55 @@
+package com.zhongzheng.common.util;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.zhongzheng.common.config.RuoYiConfig;
+import com.zhongzheng.common.filter.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 获取地址类
+ * 
+ * @author zhongzheng
+ */
+public class AddressUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
+
+    // IP地址查询
+    public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
+
+    // 未知地址
+    public static final String UNKNOWN = "XX XX";
+
+    public static String getRealAddressByIP(String ip)
+    {
+        String address = UNKNOWN;
+        // 内网不查询
+        if (IpUtils.internalIp(ip))
+        {
+            return "内网IP";
+        }
+        if (RuoYiConfig.isAddressEnabled())
+        {
+            try
+            {
+                String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK);
+                if (StrUtil.isEmpty(rspStr))
+                {
+                    log.error("获取地理位置异常 {}", ip);
+                    return UNKNOWN;
+                }
+                JSONObject obj = JSONObject.parseObject(rspStr);
+                String region = obj.getString("pro");
+                String city = obj.getString("city");
+                return String.format("%s %s", region, city);
+            }
+            catch (Exception e)
+            {
+                log.error("获取地理位置异常 {}", ip);
+            }
+        }
+        return address;
+    }
+}

+ 752 - 0
src/main/java/com/zhongzheng/common/util/DateUtils.java

@@ -0,0 +1,752 @@
+package com.zhongzheng.common.util;
+
+import cn.hutool.core.lang.Validator;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.time.DateFormatUtils;
+import org.apache.http.util.Args;
+
+import java.lang.management.ManagementFactory;
+import java.lang.ref.SoftReference;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+/**
+ * 时间工具类
+ *
+ * @author zhongzheng
+ */
+public class DateUtils extends org.apache.commons.lang3.time.DateUtils
+{
+    public static String YYYY = "yyyy";
+
+    public static String YYYY_MM = "yyyy-MM";
+
+    public static String YYYY_MM_DD = "yyyy-MM-dd";
+
+    public static String YYYYMMDD = "yyyyMMdd";
+
+    public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
+
+    public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+
+    public static String YYYY__MM__DD = "yyyy年MM月dd日";
+
+    private static String[] parsePatterns = {
+            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
+            "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
+            "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+
+    static final class DateFormatHolder {
+        private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>> THREADLOCAL_FORMATS = new ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>();
+
+        DateFormatHolder() {
+        }
+
+        public static SimpleDateFormat formatFor(String pattern) {
+            SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get();
+            Map<String, SimpleDateFormat> formats = ref == null ? null : ref.get();
+            if (formats == null) {
+                formats = new HashMap<String, SimpleDateFormat>();
+                THREADLOCAL_FORMATS.set(new SoftReference(formats));
+            }
+
+            SimpleDateFormat format = formats.get(pattern);
+
+            if (format == null) {
+                format = new SimpleDateFormat(pattern);
+                format.setTimeZone(TimeZone.getTimeZone("GMT+8"));
+                ((Map) formats).put(pattern, format);
+            }
+
+            return format;
+        }
+
+        public static void clearThreadLocal() {
+            THREADLOCAL_FORMATS.remove();
+        }
+    }
+
+    public static String formatDateV3(Date date, String pattern) {
+        Args.notNull(date, "Date");
+        Args.notNull(pattern, "Pattern");
+        SimpleDateFormat formatFor = DateFormatHolder.formatFor(pattern);
+        return formatFor.format(date);
+    }
+
+    /**
+     * 获取当前Date型日期
+     *
+     * @return Date() 当前日期
+     */
+    public static Date getNowDate()
+    {
+        return new Date();
+    }
+
+    /**
+     * 获取当前日期, 默认格式为yyyy-MM-dd
+     *
+     * @return String
+     */
+    public static String getDate()
+    {
+        return dateTimeNow(YYYY_MM_DD);
+    }
+
+    public static final String getTime()
+    {
+        return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
+    }
+
+    public static final String dateTimeNow()
+    {
+        return dateTimeNow(YYYYMMDDHHMMSS);
+    }
+
+    public static final String dateTimeNow(final String format)
+    {
+        return parseDateToStr(format, new Date());
+    }
+
+    public static final String dateTime(final Date date)
+    {
+        return parseDateToStr(YYYY_MM_DD, date);
+    }
+
+    public static final String parseDateToStr(final String format, final Date date)
+    {
+        return new SimpleDateFormat(format).format(date);
+    }
+
+    public static final Date dateTimeThrow(final String format, final String ts) throws ParseException {
+        return new SimpleDateFormat(format).parse(ts);
+    }
+
+    public static final Date dateTime(final String format, final String ts)
+    {
+        try
+        {
+            return new SimpleDateFormat(format).parse(ts);
+        }
+        catch (ParseException e)
+        {
+
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static final Long dateTimeSec(final String format, final String ts)
+    {
+        try
+        {
+            return (new SimpleDateFormat(format).parse(ts)).getTime()/1000;
+        }
+        catch (ParseException e)
+        {
+
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static String timestampToDateFormat(Long times){
+        if(Validator.isEmpty(times)){
+            return "";
+        }
+        long t = times.longValue();
+        t = t * 1000;
+        Date date = new Date(t);
+        return DateFormatUtils.format(date, "yyyy/MM/dd");
+    }
+
+    public static Date timeToDate(Long times){
+        long t = times.longValue();
+        t = t * 1000;
+        return new Date(t);
+    }
+
+    public static String timestampToDateFormatMonth(Long times){
+        if(Validator.isEmpty(times)){
+            return "";
+        }
+        long t = times.longValue();
+        t = t * 1000;
+        Date date = new Date(t);
+        return DateFormatUtils.format(date, "MM月dd号");
+    }
+
+    public static String timestampToDateFormatMonthTwo(Long times){
+        if(Validator.isEmpty(times)){
+            return "";
+        }
+        long t = times.longValue();
+        t = t * 1000;
+        Date date = new Date(t);
+        return DateFormatUtils.format(date, "MM.dd");
+    }
+
+    public static String timestampToDateFormat(Long times,String patternStr){
+        if(Validator.isEmpty(times)){
+            return "";
+        }
+        long t = times.longValue();
+        t = t * 1000;
+        Date date = new Date(t);
+        return DateFormatUtils.format(date, patternStr);
+    }
+
+
+    /**
+     * 将秒转为时分秒格式【01:01:01】
+     * @param second 需要转化的秒数
+     * @return
+     */
+    public static String secondConvertHourMinSecond(Long second) {
+        String str = "";
+        if (second == null || second < 0) {
+            return str;
+        }
+
+        // 得到小时
+        long h = second / 3600;
+        str = h > 0 ? ((h < 10 ? ("0" + h) : h) + "时") : "";
+
+        // 得到分钟
+        long m = (second % 3600) / 60;
+        str += m > 0? (m < 10 ? ("0" + m) : m) + "分":"";
+
+        //得到剩余秒
+        long s = second % 60;
+        str += s > 0?(s < 10 ? ("0" + s) : s)+"秒":"";
+        return str;
+    }
+
+    public static String timestampToDate(Long times){
+        if(Validator.isEmpty(times)){
+            return "";
+        }
+        long t = times.longValue();
+        t = t * 1000;
+        Date date = new Date(t);
+        return dateTime(date);
+    }
+
+    /**
+     * 日期路径 即年/月/日 如2018/08/08
+     */
+    public static final String datePath()
+    {
+        Date now = new Date();
+        return DateFormatUtils.format(now, "yyyy/MM/dd");
+    }
+
+    /**
+     * 日期路径 即年/月/日 如20180808
+     */
+    public static final String dateTime()
+    {
+        Date now = new Date();
+        return DateFormatUtils.format(now, "yyyyMMdd");
+    }
+
+    /**
+     * 日期型字符串转化为日期 格式
+     */
+    public static Date parseDate(Object str)
+    {
+        if (str == null)
+        {
+            return null;
+        }
+        try
+        {
+            return parseDate(str.toString(), parsePatterns);
+        }
+        catch (ParseException e)
+        {
+            return null;
+        }
+    }
+
+    /**
+     * 获取服务器启动时间
+     */
+    public static Date getServerStartDate()
+    {
+        long time = ManagementFactory.getRuntimeMXBean().getStartTime();
+        return new Date(time);
+    }
+
+    /**
+     * 计算两个时间差
+     */
+    public static String getDatePoor(Date endDate, Date nowDate)
+    {
+        long nd = 1000 * 24 * 60 * 60;
+        long nh = 1000 * 60 * 60;
+        long nm = 1000 * 60;
+        // long ns = 1000;
+        // 获得两个时间的毫秒时间差异
+        long diff = endDate.getTime() - nowDate.getTime();
+        // 计算差多少天
+        long day = diff / nd;
+        // 计算差多少小时
+        long hour = diff % nd / nh;
+        // 计算差多少分钟
+        long min = diff % nd % nh / nm;
+        // 计算差多少秒//输出结果
+        // long sec = diff % nd % nh % nm / ns;
+        return day + "天" + hour + "小时" + min + "分钟";
+    }
+
+    public static Long getNowTime()
+    {
+        return System.currentTimeMillis()/1000;
+    }
+
+    /**
+     * 获取今天凌晨时间戳
+     */
+    public static Long getTodayZeroTime()
+    {
+        Calendar cal = Calendar.getInstance();
+        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
+        return cal.getTimeInMillis() / 1000;  //今天凌晨
+    }
+
+    /**
+     * 获取指定时间的凌晨时间戳
+     */
+    public static Long getScheduleTimeStrZeroTime(String timeStr,String patternStr)
+    {
+        Long time = dateTimeSec(patternStr,timeStr);
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(timeToDate(time));
+        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
+        return cal.getTimeInMillis() / 1000;  //今天凌晨
+    }
+
+    /**
+     * 获取指定时间的凌晨时间戳
+     */
+    public static Long getScheduleTimeZeroTime(Long scheduleTime)
+    {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(timeToDate(scheduleTime));
+        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
+        return cal.getTimeInMillis() / 1000;  //今天凌晨
+    }
+
+    public static String formatDate(Date time,String str)
+    {
+       SimpleDateFormat sdf = new SimpleDateFormat(str);
+        return sdf.format(time);
+    }
+
+    /**
+     * 获取明天凌晨时间戳
+     */
+    public static Long getTomorrowZeroTime()
+    {
+        Calendar cal = Calendar.getInstance();
+        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
+        Calendar cal1 = Calendar.getInstance();
+        cal1.setTime(cal.getTime());
+        cal1.add(Calendar.DAY_OF_MONTH , +1);
+        return cal1.getTimeInMillis() / 1000; //明天凌晨
+    }
+
+    /**
+     * 获取当月第一天的凌晨时间戳
+     */
+    public static Long getToMonthZeroTime()
+    {
+        Calendar cal = Calendar.getInstance();
+        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 1, 0, 0, 0);
+        return cal.getTime().getTime()/1000;
+    }
+
+    /**
+     * 获取本年第一天的凌晨时间戳
+     */
+    public static Long getToYearZeroTime()
+    {
+        Calendar calendar = Calendar.getInstance();
+        // 设置当前时间为年初的第一天
+        calendar.set(Calendar.DAY_OF_YEAR, 1);
+        Date startOfCurrentYear = calendar.getTime();
+        return startOfCurrentYear.getTime()/1000;
+    }
+
+    /**
+     * 根据当前日期获得所在周的日期区间(周一和周日日期)
+     */
+    public static Map<String, Long> getTimeInterval(Date date){
+        Map<String, Long> map = new HashMap<>();
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        // 判断要计算的日期是否是周日,如果是则减一天计算周六的,否则会出问题,计算到下一周去了
+        int dayWeek = cal.get(Calendar.DAY_OF_WEEK);// 获得当前日期是一个星期的第几天
+        if(1 == dayWeek){
+            cal.add(Calendar.DAY_OF_MONTH,-1);
+        }
+        // 设置一个星期的第一天,按中国的习惯一个星期的第一天是星期一
+        cal.setFirstDayOfWeek(Calendar.MONDAY);
+        // 获得当前日期是一个星期的第几天
+        int day = cal.get(Calendar.DAY_OF_WEEK);
+        // 根据日历的规则,给当前日期减去星期几与一个星期第一天的差值
+        cal.add(Calendar.DATE, cal.getFirstDayOfWeek() - day);
+        Long imptimeBegin = cal.getTime().getTime();
+        cal.add(Calendar.DATE,6);
+        Long imptimeEnd = cal.getTime().getTime();
+        map.put("start", imptimeBegin/1000);
+        map.put("end", imptimeEnd/1000);
+        return map;
+    }
+
+
+    /**
+     * 根据当前日期获得上周的日期区间(上周周一和周日日期)
+     */
+    public static Map<String, Long> getLastTimeInterval(Date date){
+        Map<String, Long> map = new HashMap<>();
+        Calendar calendar1 = Calendar.getInstance();
+        Calendar calendar2 = Calendar.getInstance();
+        calendar1.setTime(date);
+        calendar2.setTime(date);
+        int dayOfWeek = calendar1.get(Calendar.DAY_OF_WEEK) - 1;
+        if(dayOfWeek <= 0){
+            dayOfWeek = 7;
+        }
+        int offset1 = 1 - dayOfWeek;
+        int offset2 = 7 - dayOfWeek;
+        calendar1.add(Calendar.DATE, offset1 - 7);
+        calendar2.add(Calendar.DATE, offset2 - 7);
+        // last Monday
+        Long lastBeginDate = calendar1.getTime().getTime();
+        // last Sunday
+        Long lastEndDate = calendar2.getTime().getTime();
+        map.put("laststart", lastBeginDate/1000);
+        map.put("lastend", lastEndDate/1000);
+        return map;
+    }
+
+    /**
+     * 获取日期格式订单号
+     * @return
+     */
+    public static String getDateOrderSn()
+    {
+        DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
+        String localDate = (LocalDateTime.now().format(ofPattern)).substring(2);
+        //随机数
+        String randomNumeric = RandomStringUtils.randomNumeric(8);
+        return localDate+randomNumeric;
+    }
+
+    public static String getDateInputOrderSn()
+    {
+        return "LD"+getDateOrderSn();
+    }
+
+
+    public static String getPayOrderSn()
+    {
+        return "P"+getDateOrderSn();
+    }
+
+    public static String getInvoiceOrderSn()
+    {
+        DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
+        String localDate = (LocalDateTime.now().format(ofPattern)).substring(2);
+        //随机数
+        String randomNumeric = RandomStringUtils.randomNumeric(4);
+        return "IN"+localDate+randomNumeric;
+
+    }
+
+    public static String getTagOrderSn(String tag)
+    {
+        return tag+getDateOrderSn();
+    }
+
+    public static String secToTime(int time) {
+        String timeStr = null;
+        int hour = 0;
+        int minute = 0;
+        int second = 0;
+        if (time <= 0)
+            return "00:00";
+        else {
+            minute = time / 60;
+            if (minute < 60) {
+                second = time % 60;
+                timeStr = "00:"+unitFormat(minute) + ":" + unitFormat(second);
+            } else {
+                hour = minute / 60;
+                if (hour > 99){
+                    return "99:59:59";
+                }
+                minute = minute % 60;
+                second = time - hour * 3600 - minute * 60;
+                timeStr = unitFormat(hour) + ":" + unitFormat(minute) + ":" + unitFormat(second);
+            }
+        }
+        return timeStr;
+    }
+
+    public static String unitFormat(int i) {
+        String retStr = null;
+        if (i >= 0 && i < 10)
+            retStr = "0" + Integer.toString(i);
+        else
+            retStr = "" + i;
+        return retStr;
+    }
+
+    public static Integer durationFormat(String duration) {
+        int index1=duration.indexOf(":");
+        int index2=duration.indexOf(":",index1+1);
+        int hh=Integer.parseInt(duration.substring(0,index1));
+        int mi=Integer.parseInt(duration.substring(index1+1,index2));
+        int ss=Integer.parseInt(duration.substring(index2+1));
+        return hh*60*60+mi*60+ss;
+    }
+
+    public static Integer dayBetween(Long s1,Long s2) {
+        String date1str = timestampToDateFormat(s1);
+        String date2str = timestampToDateFormat(s2);
+        int count = 0;
+        if("".equals(date1str) || date1str == null || "".equals(date2str) || date2str == null) {
+            return count;
+        }
+        SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
+       try {
+           Date date1 = format.parse(date1str);
+           Date date2 = format.parse(date2str);
+           count = ((int) ((date1.getTime() - date2.getTime()) / (1000*3600*24)));
+           return count;
+        }catch (Exception e){
+           return null;
+       }
+    }
+
+    public static  Integer getTodayWeek(){
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(new Date());
+        int weekIdx = calendar.get(Calendar.DAY_OF_WEEK) - 1;
+        switch (weekIdx) {
+            case 1:
+                return 1;
+            case 2:
+                return 2;
+            case 3:
+                return 3;
+            case 4:
+                return 4;
+            case 5:
+                return 5;
+            case 6:
+                return 6;
+            default:
+                return 7;
+        }
+    }
+
+
+    public static LocalDate[] getDateArray() {
+        // 创建一个长度为30的数组
+        LocalDate[] dates = new LocalDate[30];
+        // 获取今天的日期
+        LocalDate today = LocalDate.now();
+        // 用循环给数组赋值
+        for (int i = 0; i < dates.length; i++) {
+            // 用today.plusDays(i)得到第i天的日期
+            dates[i] = today.plusDays(i);
+        }
+        // 返回数组
+        return dates;
+    }
+
+    static List<String> holiday =new ArrayList<>();
+    static List<String> extraWorkDay =new ArrayList<>();
+    public static Boolean isWorkingDay(long time) {
+        LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.of("+8"));
+        String formatTime = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        initHoliday();
+        initExtraWorkDay();
+        //是否加班日
+        if(extraWorkDay.contains(formatTime)){
+            return true;
+        }
+        //是否节假日
+        if(holiday.contains(formatTime)){
+            return false;
+        }
+        //如果是1-5表示周一到周五  是工作日
+        DayOfWeek week = dateTime.getDayOfWeek();
+        if(week==DayOfWeek.SATURDAY||week==DayOfWeek.SUNDAY){
+            return false;
+        }
+        return true;
+
+    }
+
+    /**
+     *  初始化节假日
+     */
+    public static void initHoliday(){
+        holiday.add("2023-06-22");
+        holiday.add("2023-06-23");
+        holiday.add("2023-09-29");
+        holiday.add("2023-09-30");
+        holiday.add("2023-10-01");
+        holiday.add("2023-10-02");
+        holiday.add("2023-10-03");
+        holiday.add("2023-10-04");
+        holiday.add("2023-10-05");
+        holiday.add("2023-10-06");
+    }
+    /**
+     *  初始化额外加班日
+     */
+    public static void initExtraWorkDay(){
+        extraWorkDay.add("2023-06-25");
+        extraWorkDay.add("2023-10-07");
+        extraWorkDay.add("2023-10-08");
+    }
+
+
+    public static Long getAppointTime(Long millisecond, Integer day) {
+        for (Integer i = 0; i < day; i++) {
+            Long dayAfter = getDayBefore(millisecond, 1);
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTime(timeToDate(dayAfter));
+            int index = calendar.get(Calendar.DAY_OF_WEEK) - 1;
+            String[] weeks = new String[]{"星期天", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
+
+            if (weeks[index].equals("星期六") || weeks[index].equals("星期天")) {
+                day += 1;
+
+            }
+            //判断当前是否为工作日
+            if (!isWorkingDay(dayAfter)) {
+                day += 1;
+            }
+            millisecond = dayAfter;
+        }
+
+        return millisecond;
+    }
+
+    /**
+     * 指定时间往前或往后推n天
+     *
+     * @param dateTime 指定时间
+     * @param x 指定天数
+     * @return
+     */
+    public static Long getDayBefore(Long dateTime, int x) {
+        Calendar c = Calendar.getInstance();
+        Date date = new Date(dateTime*1000);
+        c.setTime(date);
+        int day = c.get(Calendar.DATE);
+        c.set(Calendar.DATE, day - x);    //往前推几天
+        //c.set(Calendar.DATE, day + x);  往后推几天
+        return c.getTime().getTime()/1000;
+    }
+
+    /**
+     * 指定时间往前或往后推n天
+     *
+     * @param dateTime 指定时间
+     * @param x 指定天数
+     * @return
+     */
+    public static Long getDayAfter(Long dateTime, int x) {
+        Calendar c = Calendar.getInstance();
+        Date date = new Date(dateTime*1000);
+        c.setTime(date);
+        int day = c.get(Calendar.DATE);
+//        c.set(Calendar.DATE, day - x);    //往前推几天
+        c.set(Calendar.DATE, day + x);  //往后推几天
+        return c.getTime().getTime()/1000;
+    }
+
+
+    public static List<Long> getWeekData(Long dataTime){
+        List<Long> week = new ArrayList();
+        Calendar calendar = Calendar.getInstance();
+        Long zeroTime = DateUtils.getScheduleTimeZeroTime(dataTime);
+        calendar.setTime(timeToDate(zeroTime));
+        // 如果是周日
+        if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
+            calendar.add(Calendar.DAY_OF_YEAR,-1);
+        }
+        // 获取当前日期是当周的第i天
+        int i = calendar.get(Calendar.DAY_OF_WEEK) - 1;
+         // 获取当前日期所在周的第一天
+        calendar.add(Calendar.DATE , -i);
+
+        for (int j = 0; j < 7; j++) {
+            if(j >0){
+                calendar.add(Calendar.DATE , 1);
+            }
+            Long time = calendar.getTime().getTime()/1000;
+            if (j == 6){
+                time = time + 86400L;
+            }
+            week.add(time);
+        }
+        return week;
+    }
+
+
+    public static Map<Long,Long> getWeekTime(Long startTime,Long endTime){
+        Map<Long,Long> mapList = new LinkedHashMap<>();
+        Long zeroTime = DateUtils.getScheduleTimeZeroTime(startTime);
+        //一天的时间戳
+        Long time = 86400L;
+        for (Long i = zeroTime; i <= endTime; i = i+time) {
+            List<Long> weekData = DateUtils.getWeekData(i);
+            mapList.put(weekData.get(0),weekData.get(weekData.size()-1));
+        }
+        return mapList;
+    }
+
+    public static Map<Long,Long> getMonthTime(Long startTime,Long endTime){
+        //一天的时间戳
+        Long time = 86400L;
+        Map<Long,Long> map = new  LinkedHashMap<>();
+        try{
+            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM");
+            Date d1 = DateUtils.timeToDate(startTime);
+            Date d2 = DateUtils.timeToDate(endTime);
+            Calendar dd = Calendar.getInstance();//定义日期实例
+            dd.setTime(d1);//设置日期起始时间
+            while (dd.getTime().before(d2)) {//判断是否到结束日期
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
+                String str = sdf.format(dd.getTime());
+                Calendar c = Calendar.getInstance();
+                c.setTime(format.parse(str));
+                c.add(Calendar.MONTH, 0);
+                c.set(Calendar.DAY_OF_MONTH,1);//设置为1号,当前日期既为本月第一天
+                Long start = c.getTime().getTime()/1000;
+                dd.setTime(DateUtils.timeToDate(start));
+                //获取当前月最后一天
+                Calendar ca = Calendar.getInstance();
+                ca.setTime(format.parse(str));
+                ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH));
+                Long end = ca.getTime().getTime()/1000 + time;
+                map.put(start,end);
+                dd.add(Calendar.MONTH, 1);//进行当前日期月份加1
+            }
+        }catch (Exception e){
+            System.out.println("异常"+e.getMessage());
+        }
+        return map;
+    }
+
+}

+ 216 - 0
src/main/java/com/zhongzheng/common/util/DictUtils.java

@@ -0,0 +1,216 @@
+package com.zhongzheng.common.util;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.StrUtil;
+import com.zhongzheng.common.croe.RedisCache;
+import com.zhongzheng.common.filter.Constants;
+import com.zhongzheng.domian.SysDictData;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 字典工具类
+ * 
+ * @author zhongzheng
+ */
+public class DictUtils
+{
+    /**
+     * 分隔符
+     */
+    public static final String SEPARATOR = ",";
+
+    /**
+     * 设置字典缓存
+     * 
+     * @param key 参数键
+     * @param dictDatas 字典数据列表
+     */
+    public static void setDictCache(String key, List<SysDictData> dictDatas)
+    {
+        SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas);
+    }
+
+    /**
+     * 设置字典缓存
+     *
+     * @param key 参数键
+     * @param dictDatas 字典数据列表
+     */
+    public static void setTopDictCache(String key, List<SysDictData> dictDatas)
+    {
+        SpringUtils.getBean(RedisCache.class).setCacheObject(getTopCacheKey(key), dictDatas);
+    }
+
+    /**
+     * 获取字典缓存
+     * 
+     * @param key 参数键
+     * @return dictDatas 字典数据列表
+     */
+    public static List<SysDictData> getDictCache(String key)
+    {
+        Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
+        if (Validator.isNotNull(cacheObj))
+        {
+            List<SysDictData> dictDatas = (List<SysDictData>)cacheObj;
+            return dictDatas;
+        }
+        return null;
+    }
+
+    /**
+     * 获取字典缓存
+     *
+     * @param key 参数键
+     * @return dictDatas 字典数据列表
+     */
+    public static List<SysDictData> getTopDictCache(String key)
+    {
+        Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(getTopCacheKey(key));
+        if (Validator.isNotNull(cacheObj))
+        {
+            List<SysDictData> dictDatas = (List<SysDictData>)cacheObj;
+            return dictDatas;
+        }
+        return null;
+    }
+
+
+    /**
+     * 根据字典类型和字典值获取字典标签
+     * 
+     * @param dictType 字典类型
+     * @param dictValue 字典值
+     * @return 字典标签
+     */
+    public static String getDictLabel(String dictType, String dictValue)
+    {
+        return getDictLabel(dictType, dictValue, SEPARATOR);
+    }
+
+    /**
+     * 根据字典类型和字典标签获取字典值
+     * 
+     * @param dictType 字典类型
+     * @param dictLabel 字典标签
+     * @return 字典值
+     */
+    public static String getDictValue(String dictType, String dictLabel)
+    {
+        return getDictValue(dictType, dictLabel, SEPARATOR);
+    }
+
+    /**
+     * 根据字典类型和字典值获取字典标签
+     * 
+     * @param dictType 字典类型
+     * @param dictValue 字典值
+     * @param separator 分隔符
+     * @return 字典标签
+     */
+    public static String getDictLabel(String dictType, String dictValue, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        List<SysDictData> datas = getDictCache(dictType);
+
+        if (StrUtil.containsAny(separator, dictValue) && CollUtil.isNotEmpty(datas))
+        {
+            for (SysDictData dict : datas)
+            {
+                for (String value : dictValue.split(separator))
+                {
+                    if (value.equals(dict.getDictValue()))
+                    {
+                        propertyString.append(dict.getDictLabel() + separator);
+                        break;
+                    }
+                }
+            }
+        }
+        else
+        {
+            for (SysDictData dict : datas)
+            {
+                if (dictValue.equals(dict.getDictValue()))
+                {
+                    return dict.getDictLabel();
+                }
+            }
+        }
+        return StrUtil.strip(propertyString.toString(), null, separator);
+    }
+
+    /**
+     * 根据字典类型和字典标签获取字典值
+     * 
+     * @param dictType 字典类型
+     * @param dictLabel 字典标签
+     * @param separator 分隔符
+     * @return 字典值
+     */
+    public static String getDictValue(String dictType, String dictLabel, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        List<SysDictData> datas = getDictCache(dictType);
+
+        if (StrUtil.containsAny(separator, dictLabel) && CollUtil.isNotEmpty(datas))
+        {
+            for (SysDictData dict : datas)
+            {
+                for (String label : dictLabel.split(separator))
+                {
+                    if (label.equals(dict.getDictLabel()))
+                    {
+                        propertyString.append(dict.getDictValue() + separator);
+                        break;
+                    }
+                }
+            }
+        }
+        else
+        {
+            for (SysDictData dict : datas)
+            {
+                if (dictLabel.equals(dict.getDictLabel()))
+                {
+                    return dict.getDictValue();
+                }
+            }
+        }
+        return StrUtil.strip(propertyString.toString(), null, separator);
+    }
+
+    /**
+     * 清空字典缓存
+     */
+    public static void clearDictCache()
+    {
+        Collection<String> keys = SpringUtils.getBean(RedisCache.class).keys(Constants.SYS_DICT_KEY + "*");
+        SpringUtils.getBean(RedisCache.class).deleteObject(keys);
+    }
+
+    /**
+     * 设置cache key
+     * 
+     * @param configKey 参数键
+     * @return 缓存键key
+     */
+    public static String getCacheKey(String configKey)
+    {
+        return Constants.SYS_DICT_KEY + configKey;
+    }
+
+    /**
+     * 设置cache key
+     *
+     * @param configKey 参数键
+     * @return 缓存键key
+     */
+    public static String getTopCacheKey(String configKey)
+    {
+        return Constants.SYS_TOP_DICT_KEY + configKey;
+    }
+}

+ 195 - 0
src/main/java/com/zhongzheng/common/util/DoubleUtils.java

@@ -0,0 +1,195 @@
+package com.zhongzheng.common.util;
+
+import java.math.BigDecimal;
+
+public class DoubleUtils {
+    // 默认除法运算精度
+    private static final Integer DEF_DIV_SCALE = 2;
+
+    /**
+     * 提供精确的加法运算。
+     *
+     * @param value1
+     *            被加数
+     * @param value2
+     *            加数
+     * @return 两个参数的和
+     */
+    public static Double add(Number value1, Number value2) {
+        return add(value1, value2, DEF_DIV_SCALE);
+    }
+
+    /**
+     * 提供精确的加法运算。
+     *
+     * @param value1
+     *            被加数
+     * @param value2
+     *            加数
+     * @param scale
+     *            表示需要精确到小数点以后几位。
+     * @return 两个参数的和
+     */
+    public static Double add(Number value1, Number value2, Integer scale) {
+        BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue()));
+        BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue()));
+        BigDecimal add = b1.add(b2);
+        BigDecimal setScale = add.setScale(scale, BigDecimal.ROUND_HALF_UP);
+        return setScale.doubleValue();
+    }
+
+    /**
+     * 提供精确的减法运算。
+     *
+     * @param value1
+     *            被减数
+     * @param value2
+     *            减数
+     * @return
+     */
+    public static Double sub(Number value1, Number value2) {
+        return sub(value1, value2, DEF_DIV_SCALE);
+    }
+
+    /**
+     *
+     * @param value1
+     *            被减数
+     * @param value2
+     *            减数
+     * @param scale
+     *            表示需要精确到小数点以后几位。
+     * @return 两个参数的差
+     */
+    public static Double sub(Number value1, Number value2, Integer scale) {
+        BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue()));
+        BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue()));
+        BigDecimal subtract = b1.subtract(b2);
+        BigDecimal setScale = subtract.setScale(scale, BigDecimal.ROUND_HALF_UP);
+        return setScale.doubleValue();
+    }
+
+    /**
+     * 提供精确的乘法运算。
+     *
+     * @param value1
+     *            被乘数
+     * @param value2
+     *            乘数
+     * @return 两个参数的积
+     */
+    public static Double mul(Number value1, Number value2) {
+        return mul(value1, value2, DEF_DIV_SCALE);
+    }
+
+    /**
+     * 提供精确的乘法运算。
+     *
+     * @param value1
+     *            被乘数
+     * @param value2
+     *            乘数
+     * @param scale
+     *            表示需要精确到小数点以后几位。
+     * @return 两个参数的积
+     */
+    public static Double mul(Number value1, Number value2, Integer scale) {
+        BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue()));
+        BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue()));
+        BigDecimal multiply = b1.multiply(b2);
+        BigDecimal setScale = multiply.setScale(scale, BigDecimal.ROUND_HALF_UP);
+        return setScale.doubleValue();
+    }
+
+    /**
+     * 提供精确的乘法运算。
+     *
+     * @param value1
+     *            被乘数
+     * @param value2
+     *            乘数
+     * @param scale
+     *            表示需要精确到小数点以后几位。
+     *
+     * @return 两个参数的积
+     */
+    public static Double mul(Number value1, Number value2, int scale, int roundingMode) {
+        BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue()));
+        BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue()));
+        BigDecimal multiply = b1.multiply(b2);
+        BigDecimal setScale = multiply.setScale(scale, roundingMode);
+        return setScale.doubleValue();
+    }
+
+    /**
+     * 提供(相对)精确的除法运算,当发生除不尽的情况时, 精确到小数点以后2位,以后的数字四舍五入。
+     *
+     * @param dividend
+     *            被除数
+     * @param divisor
+     *            除数
+     * @return 两个参数的商
+     */
+    public static Double div(Number dividend, Number divisor) {
+        return DoubleUtils.div(dividend, divisor, DEF_DIV_SCALE);
+    }
+
+    /**
+     * 提供(相对)精确的除法运算。 当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。
+     *
+     * @param dividend
+     *            被除数
+     * @param divisor
+     *            除数
+     * @param scale
+     *            表示需要精确到小数点以后几位。
+     * @return 两个参数的商
+     */
+    public static Double div(Number dividend, Number divisor, Integer scale) {
+        if (scale < 0) {
+            throw new IllegalArgumentException("The scale must be a positive integer or zero");
+        }
+        BigDecimal b1 = new BigDecimal(Double.toString(dividend.doubleValue()));
+        BigDecimal b2 = new BigDecimal(Double.toString(divisor.doubleValue()));
+        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
+    }
+
+    /**
+     * 提供(相对)精确的除法运算。 当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。
+     *
+     * @param dividend
+     *            被除数
+     * @param divisor
+     *            除数
+     * @param scale
+     *            表示需要精确到小数点以后几位。
+     * @return 两个参数的商
+     */
+    public static Double div(Number dividend, Number divisor, Integer scale, int roundingMode) {
+        if (scale < 0) {
+            throw new IllegalArgumentException("The scale must be a positive integer or zero");
+        }
+        BigDecimal b1 = new BigDecimal(Double.toString(dividend.doubleValue()));
+        BigDecimal b2 = new BigDecimal(Double.toString(divisor.doubleValue()));
+        return b1.divide(b2, scale, roundingMode).doubleValue();
+    }
+
+    /**
+     * 提供精确的小数位四舍五入处理。
+     *
+     * @param value
+     *            需要四舍五入的数字
+     * @param scale
+     *            小数点后保留几位
+     * @return 四舍五入后的结果
+     */
+    public static Double round(Double value, Integer scale) {
+        if (scale < 0) {
+            throw new IllegalArgumentException("The scale must be a positive integer or zero");
+        }
+        BigDecimal b = new BigDecimal(Double.toString(value));
+        BigDecimal one = new BigDecimal("1");
+        return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
+    }
+
+}

+ 174 - 0
src/main/java/com/zhongzheng/common/util/EasyPoiUtil.java

@@ -0,0 +1,174 @@
+package com.zhongzheng.common.util;
+
+import cn.afterturn.easypoi.excel.ExcelExportUtil;
+import cn.afterturn.easypoi.excel.ExcelImportUtil;
+import cn.afterturn.easypoi.excel.entity.ExportParams;
+import cn.afterturn.easypoi.excel.entity.ImportParams;
+import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+public class EasyPoiUtil {
+    /**
+     * 功能描述:复杂导出Excel,包括文件名以及表名。创建表头
+     *
+     * @param list 导出的实体类
+     * @param title 表头名称
+     * @param sheetName sheet表名
+     * @param pojoClass 映射的实体类
+     * @param isCreateHeader 是否创建表头
+     * @param fileName
+     * @param response
+     * @return
+     */
+    public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean isCreateHeader, HttpServletResponse response) {
+        ExportParams exportParams = new ExportParams(title, sheetName);
+        exportParams.setCreateHeadRows(isCreateHeader);
+        defaultExport(list, pojoClass, fileName, response, exportParams);
+    }
+
+
+    /**
+     * 功能描述:复杂导出Excel,包括文件名以及表名,不创建表头
+     *
+     * @param list 导出的实体类
+     * @param title 表头名称
+     * @param sheetName sheet表名
+     * @param pojoClass 映射的实体类
+     * @param fileName
+     * @param response
+     * @return
+     */
+    public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, HttpServletResponse response) {
+        defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName));
+    }
+
+    /**
+     * 功能描述:Map 集合导出
+     *
+     * @param list 实体集合
+     * @param fileName 导出的文件名称
+     * @param response
+     * @return
+     */
+    public static void exportExcel(List<Map<String, Object>> list, String fileName, HttpServletResponse response) {
+        defaultExport(list, fileName, response);
+    }
+
+    /**
+     * 功能描述:默认导出方法
+     *
+     * @param list 导出的实体集合
+     * @param fileName 导出的文件名
+     * @param pojoClass pojo实体
+     * @param exportParams ExportParams封装实体
+     * @param response
+     * @return
+     */
+    private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response, ExportParams exportParams) {
+        Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
+        if (workbook != null) {
+            downLoadExcel(fileName, response, workbook);
+        }
+    }
+
+    /**
+     * 功能描述:Excel导出
+     *
+     * @param fileName 文件名称
+     * @param response
+     * @param workbook Excel对象
+     * @return
+     */
+    private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) {
+        try {
+            response.setCharacterEncoding("UTF-8");
+            response.setHeader("content-Type", "application/vnd.ms-excel");
+            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
+            workbook.write(response.getOutputStream());
+        } catch (IOException e) {
+            throw new  RuntimeException(e);
+        }
+    }
+
+    /**
+     * 功能描述:默认导出方法
+     *
+     * @param list 导出的实体集合
+     * @param fileName 导出的文件名
+     * @param response
+     * @return
+     */
+    private static void defaultExport(List<Map<String, Object>> list, String fileName, HttpServletResponse response) {
+        Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.HSSF);
+        if (workbook != null) ;
+        downLoadExcel(fileName, response, workbook);
+    }
+
+
+    /**
+     * 功能描述:根据文件路径来导入Excel
+     *
+     * @param filePath 文件路径
+     * @param titleRows 表标题的行数
+     * @param headerRows 表头行数
+     * @param pojoClass Excel实体类
+     * @return
+     */
+    public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
+        //判断文件是否存在
+        if (StringUtils.isBlank(filePath)) {
+            return null;
+        }
+        ImportParams params = new ImportParams();
+        params.setTitleRows(titleRows);
+        params.setHeadRows(headerRows);
+        List<T> list = null;
+        try {
+            list = ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);
+        } catch (NoSuchElementException e) {
+            throw new RuntimeException("模板不能为空");
+        } catch (Exception e) {
+            e.printStackTrace();
+
+        }
+        return list;
+    }
+
+    /**
+     * 功能描述:根据接收的Excel文件来导入Excel,并封装成实体类
+     *
+     * @param file 上传的文件
+     * @param titleRows 表标题的行数
+     * @param headerRows 表头行数
+     * @param pojoClass Excel实体类
+     * @return
+     */
+    public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
+        if (file == null) {
+            return null;
+        }
+        ImportParams params = new ImportParams();
+        params.setTitleRows(titleRows);
+        params.setHeadRows(headerRows);
+        List<T> list = null;
+        try {
+            list = ExcelImportUtil.importExcel(file.getInputStream(), pojoClass, params);
+        } catch (NoSuchElementException e) {
+            throw new RuntimeException("excel文件不能为空");
+        } catch (Exception e) {
+            throw new RuntimeException("导入的文件不正确,请重新导入!");
+
+        }
+        return list;
+    }
+}

+ 1473 - 0
src/main/java/com/zhongzheng/common/util/ExcelUtil.java

@@ -0,0 +1,1473 @@
+package com.zhongzheng.common.util;
+
+import cn.afterturn.easypoi.excel.ExcelExportUtil;
+import cn.afterturn.easypoi.excel.entity.ExportParams;
+import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.StrUtil;
+import com.zhongzheng.common.annotation.Excel;
+import com.zhongzheng.common.annotation.Excel.ColumnType;
+import com.zhongzheng.common.annotation.Excel.Type;
+import com.zhongzheng.common.annotation.Excels;
+import com.zhongzheng.common.config.RuoYiConfig;
+import com.zhongzheng.common.filter.CustomException;
+import com.zhongzheng.common.model.AjaxResult;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
+import org.apache.poi.xssf.usermodel.XSSFDataValidation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Excel相关处理
+ *
+ * @author zhongzheng
+ */
+public class ExcelUtil<T>
+{
+    private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
+
+    /**
+     * Excel sheet最大行数,默认65536
+     */
+    public static final int sheetSize = 65536;
+
+    /**
+     * 工作表名称
+     */
+    private String sheetName;
+
+    /**
+     * 导出类型(EXPORT:导出数据;IMPORT:导入模板)
+     */
+    private Type type;
+
+    /**
+     * 工作薄对象
+     */
+    private Workbook wb;
+
+    /**
+     * 工作表对象
+     */
+    private Sheet sheet;
+
+    /**
+     * 样式列表
+     */
+    private Map<String, CellStyle> styles;
+
+    /**
+     * 导入导出数据列表
+     */
+    private List<T> list;
+
+    /**
+     * 注解列表
+     */
+    private List<Object[]> fields;
+
+    /**
+     * 最大高度
+     */
+    private short maxHeight;
+
+    /**
+     * 统计列表
+     */
+    private Map<Integer, Double> statistics = new HashMap<Integer, Double>();
+
+    /**
+     * 数字格式
+     */
+    private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");
+
+    /**
+     * 实体对象
+     */
+    public Class<T> clazz;
+
+    public ExcelUtil(Class<T> clazz)
+    {
+        this.clazz = clazz;
+    }
+
+    public void init(List<T> list, String sheetName, Type type)
+    {
+        if (list == null)
+        {
+            list = new ArrayList<T>();
+        }
+        this.list = list;
+        this.sheetName = sheetName;
+        this.type = type;
+        createExcelField();
+        createWorkbook();
+    }
+
+    /**
+     * 对excel表单默认第一个索引名转换成list
+     *
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public List<T> importExcel(InputStream is) throws Exception
+    {
+        return importExcel(StrUtil.EMPTY, is);
+    }
+
+    /**
+     * 对excel表单指定表格索引名转换成list
+     *
+     * @param sheetName 表格索引名
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public List<T> importExcel(String sheetName, InputStream is) throws Exception
+    {
+        this.type = Type.IMPORT;
+        this.wb = WorkbookFactory.create(is);
+        List<T> list = new ArrayList<T>();
+        Sheet sheet = null;
+        if (Validator.isNotEmpty(sheetName))
+        {
+            // 如果指定sheet名,则取指定sheet中的内容.
+            sheet = wb.getSheet(sheetName);
+        }
+        else
+        {
+            // 如果传入的sheet名不存在则默认指向第1个sheet.
+            sheet = wb.getSheetAt(0);
+        }
+
+        if (sheet == null)
+        {
+            throw new IOException("文件sheet不存在");
+        }
+
+        int rows = sheet.getPhysicalNumberOfRows();
+
+        if (rows > 0)
+        {
+            // 定义一个map用于存放excel列的序号和field.
+            Map<String, Integer> cellMap = new HashMap<String, Integer>();
+            // 获取表头
+            Row heard = sheet.getRow(0);
+            for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
+            {
+                Cell cell = heard.getCell(i);
+                if (Validator.isNotNull(cell))
+                {
+                    String value = this.getCellValue(heard, i).toString();
+                    cellMap.put(value, i);
+                }
+                else
+                {
+                    cellMap.put(null, i);
+                }
+            }
+            // 有数据时才处理 得到类的所有field.
+            Field[] allFields = clazz.getDeclaredFields();
+            // 定义一个map用于存放列的序号和field.
+            Map<Integer, Field> fieldsMap = new HashMap<Integer, Field>();
+            for (int col = 0; col < allFields.length; col++)
+            {
+                Field field = allFields[col];
+                Excel attr = field.getAnnotation(Excel.class);
+                if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
+                {
+                    // 设置类的私有字段属性可访问.
+                    field.setAccessible(true);
+                    Integer column = cellMap.get(attr.name());
+                    if (column != null)
+                    {
+                        fieldsMap.put(column, field);
+                    }
+                }
+            }
+            for (int i = 1; i < rows; i++)
+            {
+                // 从第2行开始取数据,默认第一行是表头.
+                Row row = sheet.getRow(i);
+                if(row == null)
+                {
+                    continue;
+                }
+                T entity = null;
+                for (Map.Entry<Integer, Field> entry : fieldsMap.entrySet())
+                {
+                    Object val = this.getCellValue(row, entry.getKey());
+
+                    // 如果不存在实例则新建.
+                    entity = (entity == null ? clazz.newInstance() : entity);
+                    // 从map中得到对应列的field.
+                    Field field = fieldsMap.get(entry.getKey());
+                    // 取得类型,并根据对象类型设置值.
+                    Class<?> fieldType = field.getType();
+                    if (String.class == fieldType)
+                    {
+                        String s = Convert.toStr(val);
+                        if (StrUtil.endWith(s, ".0"))
+                        {
+                        //    val = StrUtil.subBefore(s, ".0",false);
+                        }
+                        else
+                        {
+                            if (val instanceof Double)
+                            {
+                                String dateFormat = field.getAnnotation(Excel.class).dateFormat();
+                                if (Validator.isNotEmpty(dateFormat))
+                                {
+                                    val = DateUtils.parseDateToStr(dateFormat, (Date) val);
+                                }
+                                else
+                                {
+                                    val = Convert.toStr(val);
+                                }
+                            }
+                            if (val instanceof Date)
+                            {
+                                String dateFormat = field.getAnnotation(Excel.class).dateFormat();
+                                if (Validator.isNotEmpty(dateFormat))
+                                {
+                                    val = DateUtils.parseDateToStr(dateFormat, (Date) val);
+                                }
+                                else
+                                {
+                                    val = Convert.toStr(val);
+                                }
+                            }
+
+                        }
+                    }
+                    else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && Validator.isNumber(Convert.toStr(val)))
+                    {
+                        val = Convert.toInt(val);
+                    }
+                    else if (Long.TYPE == fieldType || Long.class == fieldType)
+                    {
+                        val = Convert.toLong(val);
+                    }
+                    else if (Double.TYPE == fieldType || Double.class == fieldType)
+                    {
+                        val = Convert.toDouble(val);
+                    }
+                    else if (Float.TYPE == fieldType || Float.class == fieldType)
+                    {
+                        val = Convert.toFloat(val);
+                    }
+                    else if (BigDecimal.class == fieldType)
+                    {
+                        val = Convert.toBigDecimal(val);
+                    }
+                    else if (Date.class == fieldType)
+                    {
+                        if (val instanceof String)
+                        {
+                            val = DateUtils.parseDate(val);
+                        }
+                        else if (val instanceof Double)
+                        {
+                            val = DateUtil.getJavaDate((Double) val);
+                        }
+                    }
+                    else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
+                    {
+                        val = Convert.toBool(val, false);
+                    }
+                    if (Validator.isNotNull(fieldType))
+                    {
+                        Excel attr = field.getAnnotation(Excel.class);
+                        String propertyName = field.getName();
+                        if (Validator.isNotEmpty(attr.targetAttr()))
+                        {
+                            propertyName = field.getName() + "." + attr.targetAttr();
+                        }
+                        else if (Validator.isNotEmpty(attr.readConverterExp()))
+                        {
+                            val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
+                        }
+                        else if (Validator.isNotEmpty(attr.dictType()))
+                        {
+                            val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
+                        }
+                        ReflectUtils.invokeSetter(entity, propertyName, val);
+                    }
+                }
+                list.add(entity);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 获取七大员指定表格考试时间
+     *
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public String examApplyTimeAnalysis(InputStream is,Integer index) throws Exception
+    {
+        this.type = Type.IMPORT;
+        this.wb = WorkbookFactory.create(is);
+        Sheet sheet = wb.getSheetAt(0);
+        if (sheet == null)
+        {
+            throw new IOException("文件sheet不存在");
+        }
+        // 获取表头
+        Row heard = sheet.getRow(index);
+        Iterator<Cell> cellIterator = heard.cellIterator();
+        List<String> dataList = new ArrayList<>();
+        while(cellIterator.hasNext()) {
+            Cell next = cellIterator.next();
+            String stringCellValue = next.getStringCellValue();
+            dataList.add(stringCellValue);
+        }
+        return dataList.get(dataList.size() -1);
+    }
+    /**
+     * 七大员考试回执方法
+     *
+     * @param sheetName 表格索引名
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public List<T> examApplyImportExcel(String sheetName, InputStream is) throws Exception
+    {
+        this.type = Type.IMPORT;
+        this.wb = WorkbookFactory.create(is);
+        List<T> list = new ArrayList<T>();
+        Sheet sheet = null;
+        if (Validator.isNotEmpty(sheetName))
+        {
+            // 如果指定sheet名,则取指定sheet中的内容.
+            sheet = wb.getSheet(sheetName);
+        }
+        else
+        {
+            // 如果传入的sheet名不存在则默认指向第1个sheet.
+            sheet = wb.getSheetAt(0);
+        }
+
+        if (sheet == null)
+        {
+            throw new IOException("文件sheet不存在");
+        }
+
+        int rows = sheet.getPhysicalNumberOfRows();
+
+        if (rows > 0)
+        {
+            // 定义一个map用于存放excel列的序号和field.
+            Map<String, Integer> cellMap = new HashMap<String, Integer>();
+            // 第2行是表头
+            Row heard = sheet.getRow(2);
+            for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
+            {
+                Cell cell = heard.getCell(i);
+                if (Validator.isNotNull(cell))
+                {
+                    String value = this.getCellValue(heard, i).toString();
+                    cellMap.put(value, i);
+                }
+                else
+                {
+                    cellMap.put(null, i);
+                }
+            }
+            // 有数据时才处理 得到类的所有field.
+            Field[] allFields = clazz.getDeclaredFields();
+            // 定义一个map用于存放列的序号和field.
+            Map<Integer, Field> fieldsMap = new HashMap<Integer, Field>();
+            for (int col = 0; col < allFields.length; col++)
+            {
+                Field field = allFields[col];
+                Excel attr = field.getAnnotation(Excel.class);
+                if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
+                {
+                    // 设置类的私有字段属性可访问.
+                    field.setAccessible(true);
+                    Integer column = cellMap.get(attr.name());
+                    if (column != null)
+                    {
+                        fieldsMap.put(column, field);
+                    }
+                }
+            }
+            for (int i = 3; i < rows; i++)
+            {
+                // 从第3行开始取数据,第2行是表头.
+                Row row = sheet.getRow(i);
+                if(row == null)
+                {
+                    continue;
+                }
+                T entity = null;
+                for (Map.Entry<Integer, Field> entry : fieldsMap.entrySet())
+                {
+                    Object val = this.getCellValue(row, entry.getKey());
+
+                    // 如果不存在实例则新建.
+                    entity = (entity == null ? clazz.newInstance() : entity);
+                    // 从map中得到对应列的field.
+                    Field field = fieldsMap.get(entry.getKey());
+                    // 取得类型,并根据对象类型设置值.
+                    Class<?> fieldType = field.getType();
+                    if (String.class == fieldType)
+                    {
+                        String s = Convert.toStr(val);
+                        if (StrUtil.endWith(s, ".0"))
+                        {
+                            //    val = StrUtil.subBefore(s, ".0",false);
+                        }
+                        else
+                        {
+                            if (val instanceof Double)
+                            {
+                                String dateFormat = field.getAnnotation(Excel.class).dateFormat();
+                                if (Validator.isNotEmpty(dateFormat))
+                                {
+                                    val = DateUtils.parseDateToStr(dateFormat, (Date) val);
+                                }
+                                else
+                                {
+                                    val = Convert.toStr(val);
+                                }
+                            }
+                            if (val instanceof Date)
+                            {
+                                String dateFormat = field.getAnnotation(Excel.class).dateFormat();
+                                if (Validator.isNotEmpty(dateFormat))
+                                {
+                                    val = DateUtils.parseDateToStr(dateFormat, (Date) val);
+                                }
+                                else
+                                {
+                                    val = Convert.toStr(val);
+                                }
+                            }
+
+                        }
+                    }
+                    else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && Validator.isNumber(Convert.toStr(val)))
+                    {
+                        val = Convert.toInt(val);
+                    }
+                    else if (Long.TYPE == fieldType || Long.class == fieldType)
+                    {
+                        val = Convert.toLong(val);
+                    }
+                    else if (Double.TYPE == fieldType || Double.class == fieldType)
+                    {
+                        val = Convert.toDouble(val);
+                    }
+                    else if (Float.TYPE == fieldType || Float.class == fieldType)
+                    {
+                        val = Convert.toFloat(val);
+                    }
+                    else if (BigDecimal.class == fieldType)
+                    {
+                        val = Convert.toBigDecimal(val);
+                    }
+                    else if (Date.class == fieldType)
+                    {
+                        if (val instanceof String)
+                        {
+                            val = DateUtils.parseDate(val);
+                        }
+                        else if (val instanceof Double)
+                        {
+                            val = DateUtil.getJavaDate((Double) val);
+                        }
+                    }
+                    else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
+                    {
+                        val = Convert.toBool(val, false);
+                    }
+                    if (Validator.isNotNull(fieldType))
+                    {
+                        Excel attr = field.getAnnotation(Excel.class);
+                        String propertyName = field.getName();
+                        if (Validator.isNotEmpty(attr.targetAttr()))
+                        {
+                            propertyName = field.getName() + "." + attr.targetAttr();
+                        }
+                        else if (Validator.isNotEmpty(attr.readConverterExp()))
+                        {
+                            val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
+                        }
+                        else if (Validator.isNotEmpty(attr.dictType()))
+                        {
+                            val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
+                        }
+                        ReflectUtils.invokeSetter(entity, propertyName, val);
+                    }
+                }
+                list.add(entity);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     *
+     * @param list 导出数据集合
+     * @param sheetName 工作表的名称
+     * @return 结果
+     */
+    public AjaxResult exportExcel(List<T> list, String sheetName)
+    {
+        this.init(list, sheetName, Type.EXPORT);
+        return exportExcel();
+    }
+
+    public List<Map<String, Object>> exportEasyData(List<T> list)
+    {
+        List<Map<String, Object>> sheetsList = new ArrayList<>();
+        ExportParams deptExportParams = new ExportParams();
+        deptExportParams.setSheetName("表1");
+        Map<String, Object> deptExportMap = new HashMap<>();
+        deptExportMap.put("title", deptExportParams);
+        deptExportMap.put("entity", clazz);
+        // sheet中要填充得数据
+        deptExportMap.put("data", list);
+        sheetsList.add(deptExportMap);
+        return sheetsList;
+    }
+
+    public String userProfileExport(String filePath, String sheetName)
+    {
+        OutputStream out = null;
+        try
+        {
+            FileInputStream inputStream = new FileInputStream(filePath);
+            String filename = encodingName(sheetName);
+            out = new FileOutputStream(getAbsoluteFile(filename));
+            byte[] buffer = new byte[1024];
+            int len = 0;
+            while ((len = inputStream.read(buffer)) != -1) {
+                out.write(buffer, 0, len);
+            }
+            return filename;
+        }
+        catch (Exception e)
+        {
+            log.error("导出学员资料异常{}", e.getMessage());
+            throw new CustomException("导出学员资料异常,请联系网站管理员!");
+        }
+        finally
+        {
+
+            if (wb != null)
+            {
+                try
+                {
+                    wb.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+            if (out != null)
+            {
+                try
+                {
+                    out.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+        }
+    }
+
+
+    public AjaxResult exportEasyExcel(List<Map<String, Object>> sheetsList, String sheetName)
+    {
+        OutputStream out = null;
+   //     Workbook workbook = null;
+        try
+        {
+            Workbook workbook = ExcelExportUtil.exportExcel(sheetsList, ExcelType.HSSF);
+            String filename = encodingXLSFilename(sheetName);
+            out = new FileOutputStream(getAbsoluteFile(filename));
+            workbook.write(out);
+            return AjaxResult.success(filename);
+        }
+        catch (Exception e)
+        {
+            log.error("导出Excel异常{}", e.getMessage());
+            throw new CustomException("导出Excel失败,请联系网站管理员!");
+        }
+        finally
+        {
+
+            if (wb != null)
+            {
+                try
+                {
+                    wb.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+            if (out != null)
+            {
+                try
+                {
+                    out.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+        }
+    }
+
+
+
+    public void exportEasyExcelStudy(List<Map<String, Object>> sheetsList,String path)
+    {
+        OutputStream out = null;
+        //     Workbook workbook = null;
+        try
+        {
+            Workbook workbook = ExcelExportUtil.exportExcel(sheetsList, ExcelType.HSSF);
+            File desc = new File(path);
+            if (!desc.getParentFile().exists())
+            {
+                desc.getParentFile().mkdirs();
+            }
+            out = new FileOutputStream(path);
+            workbook.write(out);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            log.error("导出Excel异常{}", e.getMessage());
+            throw new CustomException("导出Excel失败,请联系网站管理员!");
+        }
+        finally
+        {
+
+            if (wb != null)
+            {
+                try
+                {
+                    wb.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+            if (out != null)
+            {
+                try
+                {
+                    out.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     *
+     * @param sheetName 工作表的名称
+     * @return 结果
+     */
+    public AjaxResult importTemplateExcel(String sheetName)
+    {
+        this.init(null, sheetName, Type.IMPORT);
+        return exportExcel();
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     *
+     * @return 结果
+     */
+    public AjaxResult exportExcel()
+    {
+        OutputStream out = null;
+        try
+        {
+            // 取出一共有多少个sheet.
+            double sheetNo = Math.ceil(list.size() / sheetSize);
+            for (int index = 0; index <= sheetNo; index++)
+            {
+                createSheet(sheetNo, index);
+
+                // 产生一行
+                Row row = sheet.createRow(0);
+                int column = 0;
+                // 写入各个字段的列头名称
+                for (Object[] os : fields)
+                {
+                    Excel excel = (Excel) os[1];
+                    this.createCell(excel, row, column++);
+                }
+                if (Type.EXPORT.equals(type))
+                {
+                    fillExcelData(index, row);
+                    addStatisticsRow();
+                }
+            }
+            String filename = encodingFilename(sheetName);
+            out = new FileOutputStream(getAbsoluteFile(filename));
+            wb.write(out);
+            return AjaxResult.success(filename);
+        }
+        catch (Exception e)
+        {
+            log.error("导出Excel异常{}", e.getMessage());
+            throw new CustomException("导出Excel失败,请联系网站管理员!");
+        }
+        finally
+        {
+            if (wb != null)
+            {
+                try
+                {
+                    wb.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+            if (out != null)
+            {
+                try
+                {
+                    out.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 填充excel数据
+     *
+     * @param index 序号
+     * @param row 单元格行
+     */
+    public void fillExcelData(int index, Row row)
+    {
+        int startNo = index * sheetSize;
+        int endNo = Math.min(startNo + sheetSize, list.size());
+        for (int i = startNo; i < endNo; i++)
+        {
+            row = sheet.createRow(i + 1 - startNo);
+            // 得到导出对象.
+            T vo = (T) list.get(i);
+            int column = 0;
+            for (Object[] os : fields)
+            {
+                Field field = (Field) os[0];
+                Excel excel = (Excel) os[1];
+                // 设置实体类私有属性可访问
+                field.setAccessible(true);
+                this.addCell(excel, row, vo, field, column++);
+            }
+        }
+    }
+
+    /**
+     * 创建表格样式
+     *
+     * @param wb 工作薄对象
+     * @return 样式列表
+     */
+    private Map<String, CellStyle> createStyles(Workbook wb)
+    {
+        // 写入各条记录,每条记录对应excel表中的一行
+        Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
+        CellStyle style = wb.createCellStyle();
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        style.setBorderRight(BorderStyle.THIN);
+        style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderLeft(BorderStyle.THIN);
+        style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderTop(BorderStyle.THIN);
+        style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderBottom(BorderStyle.THIN);
+        style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        Font dataFont = wb.createFont();
+        dataFont.setFontName("Arial");
+        dataFont.setFontHeightInPoints((short) 10);
+        style.setFont(dataFont);
+        styles.put("data", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        Font headerFont = wb.createFont();
+        headerFont.setFontName("Arial");
+        headerFont.setFontHeightInPoints((short) 10);
+        headerFont.setBold(true);
+        headerFont.setColor(IndexedColors.WHITE.getIndex());
+        style.setFont(headerFont);
+        styles.put("header", style);
+
+        style = wb.createCellStyle();
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        Font totalFont = wb.createFont();
+        totalFont.setFontName("Arial");
+        totalFont.setFontHeightInPoints((short) 10);
+        style.setFont(totalFont);
+        styles.put("total", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(HorizontalAlignment.LEFT);
+        styles.put("data1", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(HorizontalAlignment.CENTER);
+        styles.put("data2", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(HorizontalAlignment.RIGHT);
+        styles.put("data3", style);
+
+        return styles;
+    }
+
+    /**
+     * 创建单元格
+     */
+    public Cell createCell(Excel attr, Row row, int column)
+    {
+        // 创建列
+        Cell cell = row.createCell(column);
+        // 写入列信息
+        cell.setCellValue(attr.name());
+        setDataValidation(attr, row, column);
+        cell.setCellStyle(styles.get("header"));
+        return cell;
+    }
+
+    /**
+     * 设置单元格信息
+     *
+     * @param value 单元格值
+     * @param attr 注解相关
+     * @param cell 单元格信息
+     */
+    public void setCellVo(Object value, Excel attr, Cell cell)
+    {
+        if (ColumnType.STRING == attr.cellType())
+        {
+            cell.setCellValue(Validator.isNull(value) ? attr.defaultValue() : value + attr.suffix());
+        }
+        else if (ColumnType.NUMERIC == attr.cellType())
+        {
+            if (Validator.isNotNull(value))
+            {
+                cell.setCellValue(StrUtil.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value));
+            }
+        }
+        else if (ColumnType.IMAGE == attr.cellType())
+        {
+            ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1),
+                    cell.getRow().getRowNum() + 1);
+            String imagePath = Convert.toStr(value);
+            if (Validator.isNotEmpty(imagePath))
+            {
+                byte[] data = ImageUtils.getImage(imagePath);
+                getDrawingPatriarch(cell.getSheet()).createPicture(anchor,
+                        cell.getSheet().getWorkbook().addPicture(data, getImageType(data)));
+            }
+        }
+    }
+
+    /**
+     * 获取画布
+     */
+    public static Drawing<?> getDrawingPatriarch(Sheet sheet)
+    {
+        if (sheet.getDrawingPatriarch() == null)
+        {
+            sheet.createDrawingPatriarch();
+        }
+        return sheet.getDrawingPatriarch();
+    }
+
+    /**
+     * 获取图片类型,设置图片插入类型
+     */
+    public int getImageType(byte[] value)
+    {
+        String type = FileTypeUtils.getFileExtendName(value);
+        if ("JPG".equalsIgnoreCase(type))
+        {
+            return Workbook.PICTURE_TYPE_JPEG;
+        }
+        else if ("PNG".equalsIgnoreCase(type))
+        {
+            return Workbook.PICTURE_TYPE_PNG;
+        }
+        return Workbook.PICTURE_TYPE_JPEG;
+    }
+
+    /**
+     * 创建表格样式
+     */
+    public void setDataValidation(Excel attr, Row row, int column)
+    {
+        if (attr.name().indexOf("注:") >= 0)
+        {
+            sheet.setColumnWidth(column, 6000);
+        }
+        else
+        {
+            // 设置列宽
+            sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256));
+        }
+        // 如果设置了提示信息则鼠标放上去提示.
+        if (Validator.isNotEmpty(attr.prompt()))
+        {
+            // 这里默认设了2-101列提示.
+            setXSSFPrompt(sheet, "", attr.prompt(), 1, 100, column, column);
+        }
+        // 如果设置了combo属性则本列只能选择不能输入
+        if (attr.combo().length > 0)
+        {
+            // 这里默认设了2-101列只能选择不能输入.
+            setXSSFValidation(sheet, attr.combo(), 1, 100, column, column);
+        }
+    }
+
+    /**
+     * 添加单元格
+     */
+    public Cell addCell(Excel attr, Row row, T vo, Field field, int column)
+    {
+        Cell cell = null;
+        try
+        {
+            // 设置行高
+            row.setHeight(maxHeight);
+            // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列.
+            if (attr.isExport())
+            {
+                // 创建cell
+                cell = row.createCell(column);
+                int align = attr.align().value();
+                cell.setCellStyle(styles.get("data" + (align >= 1 && align <= 3 ? align : "")));
+
+                // 用于读取对象中的属性
+                Object value = getTargetValue(vo, field, attr);
+                String dateFormat = attr.dateFormat();
+                String readConverterExp = attr.readConverterExp();
+                String separator = attr.separator();
+                String dictType = attr.dictType();
+                if (Validator.isNotEmpty(dateFormat) && Validator.isNotNull(value))
+                {
+                    cell.setCellValue(DateUtils.parseDateToStr(dateFormat, (Date) value));
+                }
+                else if (Validator.isNotEmpty(readConverterExp) && Validator.isNotNull(value))
+                {
+                    cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator));
+                }
+                else if (Validator.isNotEmpty(dictType) && Validator.isNotNull(value))
+                {
+                    cell.setCellValue(convertDictByExp(Convert.toStr(value), dictType, separator));
+                }
+                else if (value instanceof BigDecimal && -1 != attr.scale())
+                {
+                    cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString());
+                }
+                else
+                {
+                    // 设置列类型
+                    setCellVo(value, attr, cell);
+                }
+                addStatisticsData(column, Convert.toStr(value), attr);
+            }
+        }
+        catch (Exception e)
+        {
+            log.error("导出Excel失败{}", e);
+        }
+        return cell;
+    }
+
+    /**
+     * 设置 POI XSSFSheet 单元格提示
+     *
+     * @param sheet 表单
+     * @param promptTitle 提示标题
+     * @param promptContent 提示内容
+     * @param firstRow 开始行
+     * @param endRow 结束行
+     * @param firstCol 开始列
+     * @param endCol 结束列
+     */
+    public void setXSSFPrompt(Sheet sheet, String promptTitle, String promptContent, int firstRow, int endRow,
+            int firstCol, int endCol)
+    {
+        DataValidationHelper helper = sheet.getDataValidationHelper();
+        DataValidationConstraint constraint = helper.createCustomConstraint("DD1");
+        CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
+        DataValidation dataValidation = helper.createValidation(constraint, regions);
+        dataValidation.createPromptBox(promptTitle, promptContent);
+        dataValidation.setShowPromptBox(true);
+        sheet.addValidationData(dataValidation);
+    }
+
+    /**
+     * 设置某些列的值只能输入预制的数据,显示下拉框.
+     *
+     * @param sheet 要设置的sheet.
+     * @param textlist 下拉框显示的内容
+     * @param firstRow 开始行
+     * @param endRow 结束行
+     * @param firstCol 开始列
+     * @param endCol 结束列
+     * @return 设置好的sheet.
+     */
+    public void setXSSFValidation(Sheet sheet, String[] textlist, int firstRow, int endRow, int firstCol, int endCol)
+    {
+        DataValidationHelper helper = sheet.getDataValidationHelper();
+        // 加载下拉列表内容
+        DataValidationConstraint constraint = helper.createExplicitListConstraint(textlist);
+        // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
+        CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
+        // 数据有效性对象
+        DataValidation dataValidation = helper.createValidation(constraint, regions);
+        // 处理Excel兼容性问题
+        if (dataValidation instanceof XSSFDataValidation)
+        {
+            dataValidation.setSuppressDropDownArrow(true);
+            dataValidation.setShowErrorBox(true);
+        }
+        else
+        {
+            dataValidation.setSuppressDropDownArrow(false);
+        }
+
+        sheet.addValidationData(dataValidation);
+    }
+
+    /**
+     * 解析导出值 0=男,1=女,2=未知
+     *
+     * @param propertyValue 参数值
+     * @param converterExp 翻译注解
+     * @param separator 分隔符
+     * @return 解析后值
+     */
+    public static String convertByExp(String propertyValue, String converterExp, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(",");
+        for (String item : convertSource)
+        {
+            String[] itemArray = item.split("=");
+            if (StrUtil.containsAny(separator, propertyValue))
+            {
+                for (String value : propertyValue.split(separator))
+                {
+                    if (itemArray[0].equals(value))
+                    {
+                        propertyString.append(itemArray[1] + separator);
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                if (itemArray[0].equals(propertyValue))
+                {
+                    return itemArray[1];
+                }
+            }
+        }
+        return StrUtil.strip(propertyString.toString(), null,separator);
+    }
+
+    /**
+     * 反向解析值 男=0,女=1,未知=2
+     *
+     * @param propertyValue 参数值
+     * @param converterExp 翻译注解
+     * @param separator 分隔符
+     * @return 解析后值
+     */
+    public static String reverseByExp(String propertyValue, String converterExp, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(",");
+        for (String item : convertSource)
+        {
+            String[] itemArray = item.split("=");
+            if (StrUtil.containsAny(separator, propertyValue))
+            {
+                for (String value : propertyValue.split(separator))
+                {
+                    if (itemArray[1].equals(value))
+                    {
+                        propertyString.append(itemArray[0] + separator);
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                if (itemArray[1].equals(propertyValue))
+                {
+                    return itemArray[0];
+                }
+            }
+        }
+        return StrUtil.strip(propertyString.toString(), null,separator);
+    }
+
+    /**
+     * 解析字典值
+     *
+     * @param dictValue 字典值
+     * @param dictType 字典类型
+     * @param separator 分隔符
+     * @return 字典标签
+     */
+    public static String convertDictByExp(String dictValue, String dictType, String separator)
+    {
+        return DictUtils.getDictLabel(dictType, dictValue, separator);
+    }
+
+    /**
+     * 反向解析值字典值
+     *
+     * @param dictLabel 字典标签
+     * @param dictType 字典类型
+     * @param separator 分隔符
+     * @return 字典值
+     */
+    public static String reverseDictByExp(String dictLabel, String dictType, String separator)
+    {
+        return DictUtils.getDictValue(dictType, dictLabel, separator);
+    }
+
+    /**
+     * 合计统计信息
+     */
+    private void addStatisticsData(Integer index, String text, Excel entity)
+    {
+        if (entity != null && entity.isStatistics())
+        {
+            Double temp = 0D;
+            if (!statistics.containsKey(index))
+            {
+                statistics.put(index, temp);
+            }
+            try
+            {
+                temp = Double.valueOf(text);
+            }
+            catch (NumberFormatException e)
+            {
+            }
+            statistics.put(index, statistics.get(index) + temp);
+        }
+    }
+
+    /**
+     * 创建统计行
+     */
+    public void addStatisticsRow()
+    {
+        if (statistics.size() > 0)
+        {
+            Cell cell = null;
+            Row row = sheet.createRow(sheet.getLastRowNum() + 1);
+            Set<Integer> keys = statistics.keySet();
+            cell = row.createCell(0);
+            cell.setCellStyle(styles.get("total"));
+            cell.setCellValue("合计");
+
+            for (Integer key : keys)
+            {
+                cell = row.createCell(key);
+                cell.setCellStyle(styles.get("total"));
+                cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key)));
+            }
+            statistics.clear();
+        }
+    }
+
+    /**
+     * 编码文件名
+     */
+    public String encodingXLSFilename(String filename)
+    {
+        filename = UUID.randomUUID().toString() + "_" + filename + ".xls";
+        return filename;
+    }
+
+    /**
+     * 编码文件名
+     */
+    public String encodingName(String filename)
+    {
+        filename = UUID.randomUUID().toString() + "_" + filename;
+        return filename;
+    }
+
+    public String encodingFilename(String filename)
+    {
+        filename = UUID.randomUUID().toString() + "_" + filename + ".xlsx";
+        return filename;
+    }
+
+    /**
+     * 获取下载路径
+     *
+     * @param filename 文件名称
+     */
+    public String getAbsoluteFile(String filename)
+    {
+        String downloadPath = RuoYiConfig.getDownloadPath() + filename;
+        File desc = new File(downloadPath);
+        if (!desc.getParentFile().exists())
+        {
+            desc.getParentFile().mkdirs();
+        }
+        return downloadPath;
+    }
+
+    /**
+     * 获取bean中的属性值
+     *
+     * @param vo 实体对象
+     * @param field 字段
+     * @param excel 注解
+     * @return 最终的属性值
+     * @throws Exception
+     */
+    private Object getTargetValue(T vo, Field field, Excel excel) throws Exception
+    {
+        Object o = field.get(vo);
+        if (Validator.isNotEmpty(excel.targetAttr()))
+        {
+            String target = excel.targetAttr();
+            if (target.contains("."))
+            {
+                String[] targets = target.split("[.]");
+                for (String name : targets)
+                {
+                    o = getValue(o, name);
+                }
+            }
+            else
+            {
+                o = getValue(o, target);
+            }
+        }
+        return o;
+    }
+
+    /**
+     * 以类的属性的get方法方法形式获取值
+     *
+     * @param o
+     * @param name
+     * @return value
+     * @throws Exception
+     */
+    private Object getValue(Object o, String name) throws Exception
+    {
+        if (Validator.isNotNull(o) && Validator.isNotEmpty(name))
+        {
+            Class<?> clazz = o.getClass();
+            Field field = clazz.getDeclaredField(name);
+            field.setAccessible(true);
+            o = field.get(o);
+        }
+        return o;
+    }
+
+    /**
+     * 得到所有定义字段
+     */
+    private void createExcelField()
+    {
+        this.fields = new ArrayList<Object[]>();
+        List<Field> tempFields = new ArrayList<>();
+        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
+        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
+        for (Field field : tempFields)
+        {
+            // 单注解
+            if (field.isAnnotationPresent(Excel.class))
+            {
+                putToField(field, field.getAnnotation(Excel.class));
+            }
+
+            // 多注解
+            if (field.isAnnotationPresent(Excels.class))
+            {
+                Excels attrs = field.getAnnotation(Excels.class);
+                Excel[] excels = attrs.value();
+                for (Excel excel : excels)
+                {
+                    putToField(field, excel);
+                }
+            }
+        }
+        this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
+        this.maxHeight = getRowHeight();
+    }
+
+    /**
+     * 根据注解获取最大行高
+     */
+    public short getRowHeight()
+    {
+        double maxHeight = 0;
+        for (Object[] os : this.fields)
+        {
+            Excel excel = (Excel) os[1];
+            maxHeight = maxHeight > excel.height() ? maxHeight : excel.height();
+        }
+        return (short) (maxHeight * 20);
+    }
+
+    /**
+     * 放到字段集合中
+     */
+    private void putToField(Field field, Excel attr)
+    {
+        if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
+        {
+            this.fields.add(new Object[] { field, attr });
+        }
+    }
+
+    /**
+     * 创建一个工作簿
+     */
+    public void createWorkbook()
+    {
+        this.wb = new SXSSFWorkbook(500);
+    }
+
+    /**
+     * 创建工作表
+     *
+     * @param sheetNo sheet数量
+     * @param index 序号
+     */
+    public void createSheet(double sheetNo, int index)
+    {
+        this.sheet = wb.createSheet();
+        this.styles = createStyles(wb);
+        // 设置工作表的名称.
+        if (sheetNo == 0)
+        {
+            wb.setSheetName(index, sheetName);
+        }
+        else
+        {
+            wb.setSheetName(index, sheetName + index);
+        }
+    }
+
+    /**
+     * 获取单元格值
+     *
+     * @param row 获取的行
+     * @param column 获取单元格列号
+     * @return 单元格值
+     */
+    public Object getCellValue(Row row, int column)
+    {
+        if (row == null)
+        {
+            return row;
+        }
+        Object val = "";
+        try
+        {
+            Cell cell = row.getCell(column);
+            if (Validator.isNotNull(cell))
+            {
+                if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA)
+                {
+                    val = cell.getNumericCellValue();
+                    if (DateUtil.isCellDateFormatted(cell))
+                    {
+                       /* SimpleDateFormat sdf  = new SimpleDateFormat("yyyy/MM/dd");
+                        Date date = DateUtil.getJavaDate((Double) val);// POI Excel 日期格式转换
+                        val = sdf.format(date);*/
+                        val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换
+                    }
+                    else
+                    {
+                        if ((Double) val % 1 != 0)
+                        {
+                            val = new BigDecimal(val.toString());
+                        }
+                        else
+                        {
+                            val = new DecimalFormat("0").format(val);
+                        }
+                    }
+                }
+                else if (cell.getCellType() == CellType.STRING)
+                {
+                    val = cell.getStringCellValue();
+                }
+                else if (cell.getCellType() == CellType.BOOLEAN)
+                {
+                    val = cell.getBooleanCellValue();
+                }
+                else if (cell.getCellType() == CellType.ERROR)
+                {
+                    val = cell.getErrorCellValue();
+                }
+
+            }
+        }
+        catch (Exception e)
+        {
+            return val;
+        }
+        return val;
+    }
+}

+ 77 - 0
src/main/java/com/zhongzheng/common/util/FileTypeUtils.java

@@ -0,0 +1,77 @@
+package com.zhongzheng.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+
+/**
+ * 文件类型工具类
+ *
+ * @author zhongzheng
+ */
+public class FileTypeUtils
+{
+    /**
+     * 获取文件类型
+     * <p>
+     * 例如: ruoyi.txt, 返回: txt
+     * 
+     * @param file 文件名
+     * @return 后缀(不含".")
+     */
+    public static String getFileType(File file)
+    {
+        if (null == file)
+        {
+            return StringUtils.EMPTY;
+        }
+        return getFileType(file.getName());
+    }
+
+    /**
+     * 获取文件类型
+     * <p>
+     * 例如: ruoyi.txt, 返回: txt
+     *
+     * @param fileName 文件名
+     * @return 后缀(不含".")
+     */
+    public static String getFileType(String fileName)
+    {
+        int separatorIndex = fileName.lastIndexOf(".");
+        if (separatorIndex < 0)
+        {
+            return "";
+        }
+        return fileName.substring(separatorIndex + 1).toLowerCase();
+    }
+
+    /**
+     * 获取文件类型
+     * 
+     * @param photoByte 文件字节码
+     * @return 后缀(不含".")
+     */
+    public static String getFileExtendName(byte[] photoByte)
+    {
+        String strFileExtendName = "JPG";
+        if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
+                && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97))
+        {
+            strFileExtendName = "GIF";
+        }
+        else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70))
+        {
+            strFileExtendName = "JPG";
+        }
+        else if ((photoByte[0] == 66) && (photoByte[1] == 77))
+        {
+            strFileExtendName = "BMP";
+        }
+        else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71))
+        {
+            strFileExtendName = "PNG";
+        }
+        return strFileExtendName;
+    }
+}

+ 389 - 0
src/main/java/com/zhongzheng/common/util/FileUploadUtils.java

@@ -0,0 +1,389 @@
+package com.zhongzheng.common.util;
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import com.zhongzheng.common.config.RuoYiConfig;
+import com.zhongzheng.common.filter.Constants;
+import com.zhongzheng.common.filter.FileNameLengthLimitExceededException;
+import com.zhongzheng.common.filter.FileSizeLimitExceededException;
+import com.zhongzheng.common.filter.InvalidExtensionException;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 文件上传工具类
+ *
+ * @author zhongzheng
+ */
+public class FileUploadUtils
+{
+    /**
+     * 默认大小 50M
+     */
+    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
+
+    /**
+     * 默认的文件名最大长度 100
+     */
+    public static final int DEFAULT_FILE_NAME_LENGTH = 100;
+
+    /**
+     * 默认上传的地址
+     */
+    private static String defaultBaseDir = RuoYiConfig.getProfile();
+
+    public static void setDefaultBaseDir(String defaultBaseDir)
+    {
+        FileUploadUtils.defaultBaseDir = defaultBaseDir;
+    }
+
+    public static String getDefaultBaseDir()
+    {
+        return defaultBaseDir;
+    }
+
+    /**
+     * 以默认配置进行文件上传
+     *
+     * @param file 上传的文件
+     * @return 文件名称
+     * @throws Exception
+     */
+    public static final String upload(MultipartFile file) throws IOException
+    {
+        try
+        {
+            return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+        }
+        catch (Exception e)
+        {
+            throw new IOException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 根据文件路径上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file 上传的文件
+     * @return 文件名称
+     * @throws IOException
+     */
+    public static final String upload(String baseDir, MultipartFile file) throws IOException
+    {
+        try
+        {
+            return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+        }
+        catch (Exception e)
+        {
+            throw new IOException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file 上传的文件
+     * @param allowedExtension 上传文件类型
+     * @return 返回上传成功的文件名
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws FileNameLengthLimitExceededException 文件名太长
+     * @throws IOException 比如读写文件出错时
+     * @throws InvalidExtensionException 文件校验异常
+     */
+    public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
+            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
+            InvalidExtensionException
+    {
+        int fileNamelength = file.getOriginalFilename().length();
+        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
+        {
+            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
+        }
+
+        assertAllowed(file, allowedExtension);
+
+        String fileName = extractFilename(file);
+
+        File desc = getAbsoluteFile(baseDir, fileName);
+        file.transferTo(desc);
+        String pathFileName = getPathFileName(baseDir, fileName);
+        return pathFileName;
+    }
+
+
+    /**
+     * 根据文件路径上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file 上传的文件
+     * @return 文件名称
+     * @throws IOException
+     */
+    public static final String uploadFragment(String baseDir, MultipartFile file,String fileName) throws IOException
+    {
+        try
+        {
+            return uploadFragment(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION,fileName);
+        }
+        catch (Exception e)
+        {
+            throw new IOException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file 上传的文件
+     * @param allowedExtension 上传文件类型
+     * @return 返回上传成功的文件名
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws FileNameLengthLimitExceededException 文件名太长
+     * @throws IOException 比如读写文件出错时
+     * @throws InvalidExtensionException 文件校验异常
+     */
+    public static final String uploadFragment(String baseDir, MultipartFile file, String[] allowedExtension,String fileName)
+            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
+            InvalidExtensionException
+    {
+        int fileNamelength = file.getOriginalFilename().length();
+        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
+        {
+            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
+        }
+
+        assertAllowedFragment(file, allowedExtension,fileName);
+        File desc = getAbsoluteFile(baseDir, fileName);
+        file.transferTo(desc);
+        String pathFileName = getPathFileName(baseDir, fileName);
+        return pathFileName;
+    }
+
+    /**
+     * 编码文件名
+     */
+    public static final String extractFilename(MultipartFile file)
+    {
+        String fileName = file.getOriginalFilename();
+        String extension = getExtension(file);
+        fileName = DateUtils.datePath() + "/" + IdUtil.fastUUID() + "." + extension;
+        return fileName;
+    }
+
+    private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
+    {
+        File desc = new File(uploadDir + File.separator + fileName);
+
+        if (!desc.getParentFile().exists())
+        {
+            desc.getParentFile().mkdirs();
+        }
+        // 解决undertow文件上传bug,因底层实现不同,无需创建新文件
+//        if (!desc.exists())
+//        {
+//            desc.createNewFile();
+//        }
+        return desc;
+    }
+
+    private static final String getPathFileName(String uploadDir, String fileName) throws IOException
+    {
+        int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
+        String currentDir = StrUtil.subSuf(uploadDir, dirLastIndex);
+        String pathFileName = Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
+        return pathFileName;
+    }
+
+    /**
+     * 文件大小校验
+     *
+     * @param file 上传的文件
+     * @return
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws InvalidExtensionException
+     */
+    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
+            throws FileSizeLimitExceededException, InvalidExtensionException
+    {
+        long size = file.getSize();
+        if (DEFAULT_MAX_SIZE != -1 && size > DEFAULT_MAX_SIZE)
+        {
+            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
+        }
+
+        String fileName = file.getOriginalFilename();
+        String extension = getExtension(file);
+        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
+        {
+            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else
+            {
+                throw new InvalidExtensionException(allowedExtension, extension, fileName);
+            }
+        }
+
+    }
+
+    /**
+     * 文件大小校验
+     *
+     * @param file 上传的文件
+     * @return
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws InvalidExtensionException
+     */
+    public static final void assertAllowedFragment(MultipartFile file, String[] allowedExtension,String fileName)
+            throws FileSizeLimitExceededException, InvalidExtensionException
+    {
+        long size = file.getSize();
+        if (DEFAULT_MAX_SIZE != -1 && size > DEFAULT_MAX_SIZE)
+        {
+            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
+        }
+
+        String extension = getExtensionFragment(fileName);
+        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
+        {
+            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else
+            {
+                throw new InvalidExtensionException(allowedExtension, extension, fileName);
+            }
+        }
+
+    }
+
+    /**
+     * 判断MIME类型是否是允许的MIME类型
+     *
+     * @param extension
+     * @param allowedExtension
+     * @return
+     */
+    public static final boolean isAllowedExtension(String extension, String[] allowedExtension)
+    {
+        for (String str : allowedExtension)
+        {
+            if (str.equalsIgnoreCase(extension))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 获取文件名的后缀
+     *
+     * @param fileName 表单文件
+     * @return 后缀名
+     */
+    public static final String getExtensionFragment(String fileName)
+    {
+        String extension = FilenameUtils.getExtension(fileName);
+        if (Validator.isEmpty(extension))
+        {
+            extension = MimeTypeUtils.getExtension(fileName);
+        }
+        return extension;
+    }
+
+    /**
+     * 获取文件名的后缀
+     *
+     * @param file 表单文件
+     * @return 后缀名
+     */
+    public static final String getExtension(MultipartFile file)
+    {
+        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
+        if (Validator.isEmpty(extension))
+        {
+            extension = MimeTypeUtils.getExtension(file.getContentType());
+        }
+        return extension;
+    }
+
+    public static void merge(String fromSign,String from, String to) throws IOException {
+        File t = new File(to);
+        FileInputStream in = null;
+        FileChannel inChannel = null;
+
+        FileOutputStream out = new FileOutputStream(t, true);
+        FileChannel outChannel = out.getChannel();
+
+        File f = new File(from);
+        // 获取目录下的每一个文件名,再将每个文件一次写入目标文件
+        if (f.isDirectory()) {
+            List<File> list = getAllFileAndSort(from,fromSign);
+            // 记录新文件最后一个数据的位置
+            long start = 0;
+            for (File file : list) {
+
+                in = new FileInputStream(file);
+                inChannel = in.getChannel();
+
+                // 从inChannel中读取file.length()长度的数据,写入outChannel的start处
+                outChannel.transferFrom(inChannel, start, file.length());
+                start += file.length();
+                in.close();
+                inChannel.close();
+            }
+        }
+        out.close();
+        outChannel.close();
+    }
+
+    private static List<File> getAllFileAndSort(String dirPath,String fromSign) {
+        File dirFile = new File(dirPath);
+        List<File> list =  Arrays.stream(dirFile.listFiles()).filter(item -> item.getName().contains(fromSign)).collect(Collectors.toList());
+        Collections.sort(list, (o1, o2) -> {
+            return Integer.parseInt(o1.getName().substring(o1.getName().indexOf("_")+1,o1.getName().indexOf(".")))
+                    - Integer.parseInt(o2.getName().substring(o2.getName().indexOf("_")+1,o2.getName().indexOf(".")));
+        });
+        return list;
+    }
+}

+ 569 - 0
src/main/java/com/zhongzheng/common/util/FileUtils.java

@@ -0,0 +1,569 @@
+package com.zhongzheng.common.util;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import com.zhongzheng.common.config.RuoYiConfig;
+import com.zhongzheng.common.filter.CustomException;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.MediaType;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.commons.CommonsMultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.math.BigInteger;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+import java.util.Comparator;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * 文件处理工具类
+ *
+ * @author zhongzheng
+ */
+public class FileUtils extends org.apache.commons.io.FileUtils
+{
+
+    private static final byte[] buf = new byte[1024];
+    public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";
+
+    /**
+     * 输出指定文件的byte数组
+     *
+     * @param filePath 文件路径
+     * @param os 输出流
+     * @return
+     */
+    public static void writeBytes(String filePath, OutputStream os) throws IOException
+    {
+        FileInputStream fis = null;
+        try
+        {
+            File file = new File(filePath);
+            if (!file.exists())
+            {
+                throw new FileNotFoundException(filePath);
+            }
+            fis = new FileInputStream(file);
+            byte[] b = new byte[1024];
+            int length;
+            while ((length = fis.read(b)) > 0)
+            {
+                os.write(b, 0, length);
+            }
+        }
+        catch (IOException e)
+        {
+            throw e;
+        }
+        finally
+        {
+            if (os != null)
+            {
+                try
+                {
+                    os.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+            if (fis != null)
+            {
+                try
+                {
+                    fis.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 删除文件
+     *
+     * @param filePath 文件
+     * @return
+     */
+    public static boolean deleteFile(String filePath)
+    {
+        boolean flag = false;
+        File file = new File(filePath);
+        // 路径为文件且不为空则进行删除
+        if (file.isFile() && file.exists())
+        {
+            file.delete();
+            flag = true;
+        }
+        return flag;
+    }
+
+    /**
+     * 文件名称验证
+     *
+     * @param filename 文件名称
+     * @return true 正常 false 非法
+     */
+    public static boolean isValidFilename(String filename)
+    {
+        return filename.matches(FILENAME_PATTERN);
+    }
+
+    /**
+     * 检查文件是否可下载
+     *
+     * @param resource 需要下载的文件
+     * @return true 正常 false 非法
+     */
+    public static boolean checkAllowDownload(String resource)
+    {
+        // 禁止目录上跳级别
+        if (StrUtil.contains(resource, ".."))
+        {
+            return false;
+        }
+
+        // 检查允许下载的文件规则
+        if (ArrayUtil.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)))
+        {
+            return true;
+        }
+
+        // 不在允许下载的文件规则
+        return false;
+    }
+
+    /**
+     * 下载文件名重新编码
+     *
+     * @param request 请求对象
+     * @param fileName 文件名
+     * @return 编码后的文件名
+     */
+    public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException
+    {
+        final String agent = request.getHeader("USER-AGENT");
+        String filename = fileName;
+        if (agent.contains("MSIE"))
+        {
+            // IE浏览器
+            filename = URLEncoder.encode(filename, "utf-8");
+            filename = filename.replace("+", " ");
+        }
+        else if (agent.contains("Firefox"))
+        {
+            // 火狐浏览器
+            filename = new String(fileName.getBytes(), "ISO8859-1");
+        }
+        else if (agent.contains("Chrome"))
+        {
+            // google浏览器
+            filename = URLEncoder.encode(filename, "utf-8");
+        }
+        else
+        {
+            // 其它浏览器
+            filename = URLEncoder.encode(filename, "utf-8");
+        }
+        return filename;
+    }
+
+    /**
+     * 下载文件名重新编码
+     *
+     * @param response 响应对象
+     * @param realFileName 真实文件名
+     * @return
+     */
+    public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException
+    {
+        String percentEncodedFileName = percentEncode(realFileName);
+
+        StringBuilder contentDispositionValue = new StringBuilder();
+        contentDispositionValue.append("attachment; filename=")
+                .append(percentEncodedFileName)
+                .append(";")
+                .append("filename*=")
+                .append("utf-8''")
+                .append(percentEncodedFileName);
+
+        response.setHeader("Content-disposition", contentDispositionValue.toString());
+    }
+
+    /**
+     * 百分号编码工具方法
+     *
+     * @param s 需要百分号编码的字符串
+     * @return 百分号编码后的字符串
+     */
+    public static String percentEncode(String s) throws UnsupportedEncodingException
+    {
+        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
+        return encode.replaceAll("\\+", "%20");
+    }
+
+    public static String encodingZipFilename(String prefix)
+    {
+        String  filename = prefix+"_"+ UUID.randomUUID().toString() + ".zip";
+        return filename;
+    }
+
+    public static String getZipAbsoluteFile(String filename)
+    {
+        String downloadPath = RuoYiConfig.getDownloadPath() + filename;
+        File desc = new File(downloadPath);
+        if (!desc.getParentFile().exists())
+        {
+            desc.getParentFile().mkdirs();
+        }
+        return downloadPath;
+    }
+
+    public static void deleteDirectoryStream(Path path) {
+        try {
+            Files.delete(path);
+            System.out.printf("删除文件成功:%s%n",path.toString());
+        } catch (IOException e) {
+            System.err.printf("无法删除的路径 %s%n%s", path, e);
+        }
+    }
+    
+    /**
+     * File转MultipartFile
+     * @author change
+     * @date 2023/4/10 11:09
+     * @param file 
+     * @return org.springframework.web.multipart.MultipartFile
+     */
+    public static MultipartFile getMultipartFile(File file) {
+        FileItem item = new DiskFileItemFactory().createItem("file"
+                , MediaType.MULTIPART_FORM_DATA_VALUE
+                , true
+                , file.getName());
+        try (InputStream input = new FileInputStream(file);
+             OutputStream os = item.getOutputStream()) {
+            // 流转移
+            IOUtils.copy(input, os);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new IllegalArgumentException("Invalid file: " + e, e);
+        }
+
+        return new CommonsMultipartFile(item);
+    }
+
+    /**
+     * 获取文件md5值
+     */
+    public static String md5HashCode(String filePath) {
+        try {
+            InputStream fis = new FileInputStream(filePath);
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            byte[] buffer = new byte[1024];
+            int length = -1;
+            while ((length = fis.read(buffer, 0, 1024)) != -1) {
+                md.update(buffer, 0, length);
+            }
+            fis.close();
+            //转换并返回包含16个元素字节数组,返回数值范围为-128到127
+            byte[] md5Bytes = md.digest();
+            BigInteger bigInt = new BigInteger(1, md5Bytes);
+            return bigInt.toString(16);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    public static void deleteFilePackage(String path){
+        //删除本地资源
+        Path pathStr = Paths.get(path);
+        try (Stream<Path> walk = Files.walk(pathStr)) {
+            walk.sorted(Comparator.reverseOrder())
+                    .forEach(FileUtils::deleteDirectoryStream);
+        }catch (IOException e) {
+            e.printStackTrace();
+            throw new CustomException(e.getMessage());
+        }
+    }
+
+    /**
+     * 压缩成ZIP 方法1
+     *
+     * @param zipFileName       压缩文件夹路径
+     * @param sourceFileName    要压缩的文件路径
+     * @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构;
+     *                         false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
+     * @throws RuntimeException 压缩失败会抛出运行时异常
+     */
+    public static Boolean toZip(String zipFileName, String sourceFileName, boolean KeepDirStructure) {
+        Boolean result = true;
+        long start = System.currentTimeMillis();//开始
+        ZipOutputStream zos = null;
+        try {
+            FileOutputStream fileOutputStream = new FileOutputStream(zipFileName);
+            zos = new ZipOutputStream(fileOutputStream);
+            File sourceFile = new File(sourceFileName);
+            compress(sourceFile, zos, sourceFile.getName(), KeepDirStructure);
+            long end = System.currentTimeMillis();//结束
+            System.out.println("压缩完成,耗时:" + (end - start) + " 毫秒");
+        } catch (Exception e) {
+            result = false;
+            e.printStackTrace();
+        } finally {
+            if (zos != null) {
+                try {
+                    zos.close();
+                } catch (IOException e) {
+                    e.getStackTrace();
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @Title: compress
+     * @Description: TODO
+     * @param filePaths 需要压缩的文件地址列表(绝对路径)
+     * @param zipFilePath 需要压缩到哪个zip文件(无需创建这样一个zip,只需要指定一个全路径)
+     * @param keepDirStructure 压缩后目录是否保持原目录结构
+     * @throws IOException
+     * @return int   压缩成功的文件个数
+     */
+    public static Boolean compressList(List<String> filePaths, String zipFilePath, Boolean keepDirStructure){
+        byte[] buf = new byte[1024];
+        File zipFile = new File(zipFilePath);
+        int fileCount = 0;//记录压缩了几个文件?
+        try {
+            //zip文件不存在,则创建文件,用于压缩
+            if(!zipFile.exists()){
+                zipFile.createNewFile();
+            }
+            ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
+            for(int i = 0; i < filePaths.size(); i++){
+                String relativePath = filePaths.get(i);
+                if(StringUtils.isEmpty(relativePath)){
+                    continue;
+                }
+                File sourceFile = new File(relativePath);//绝对路径找到file
+                if(sourceFile == null || !sourceFile.exists()){
+                    continue;
+                }
+
+                FileInputStream fis = new FileInputStream(sourceFile);
+                if(keepDirStructure!=null && keepDirStructure){
+                    //保持目录结构
+                    zos.putNextEntry(new ZipEntry(relativePath));
+                }else{
+                    //直接放到压缩包的根目录
+                    zos.putNextEntry(new ZipEntry(sourceFile.getName()));
+                }
+                //System.out.println("压缩当前文件:"+sourceFile.getName());
+                int len;
+                while((len = fis.read(buf)) > 0){
+                    zos.write(buf, 0, len);
+                }
+                zos.closeEntry();
+                fis.close();
+                fileCount++;
+            }
+            zos.close();
+            //System.out.println("压缩完成");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return fileCount > 0;
+    }
+
+
+    /**
+     * 递归压缩方法
+     *
+     * @param sourceFile       源文件
+     * @param zos              zip输出流
+     * @param name             压缩后的名称
+     * @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构;
+     *                         false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
+     * @throws Exception
+     */
+    public static void compress(File sourceFile, ZipOutputStream zos, String name,
+                                boolean KeepDirStructure) throws Exception {
+
+        if (sourceFile.isFile()) {
+            // 向zip输出流中添加一个zip实体,构造器中name为zip实体的文件的名字
+            zos.putNextEntry(new ZipEntry(name));
+            // copy文件到zip输出流中
+            int len;
+            FileInputStream in = new FileInputStream(sourceFile);
+            while ((len = in.read(buf)) != -1) {
+                zos.write(buf, 0, len);
+            }
+            // Complete the entry
+            zos.closeEntry();
+            in.close();
+        } else {
+            File[] listFiles = sourceFile.listFiles();
+            if (listFiles == null || listFiles.length == 0) {
+                // 需要保留原来的文件结构时,需要对空文件夹进行处理
+                if (KeepDirStructure) {
+                    // 空文件夹的处理
+                    zos.putNextEntry(new ZipEntry(name + "/"));
+                    // 没有文件,不需要文件的copy
+                    zos.closeEntry();
+                }
+            } else {
+                for (File file : listFiles) {
+                    // 判断是否需要保留原来的文件结构
+                    if (KeepDirStructure) {
+                        // 注意:file.getName()前面需要带上父文件夹的名字加一斜杠,
+                        // 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了
+                        compress(file, zos, name + "/" + file.getName(), KeepDirStructure);
+                    } else {
+                        compress(file, zos, file.getName(), KeepDirStructure);
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 解压
+     *
+     * @param inputStream zip压缩文件
+     * @param descDir 指定的解压目录
+     */
+    public static void unzipWithStream(InputStream inputStream, String descDir) {
+        if (!descDir.endsWith(File.separator)) {
+            descDir = descDir + File.separator;
+        }
+        try (ZipInputStream zipInputStream = new ZipInputStream(inputStream, Charset.forName("GBK"))) {
+            ZipEntry zipEntry;
+            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
+                String zipEntryNameStr = zipEntry.getName();
+                String zipEntryName = zipEntryNameStr;
+                if (zipEntryNameStr.contains("/")) {
+                    String str1 = zipEntryNameStr.substring(0, zipEntryNameStr.indexOf("/"));
+                    zipEntryName = zipEntryNameStr.substring(str1.length() + 1);
+                }
+                String outPath = (descDir + zipEntryName).replace("\\\\", "/");
+                File outFile = new File(outPath.substring(0, outPath.lastIndexOf('/')));
+                if (!outFile.exists()) {
+                    outFile.mkdirs();
+                }
+                if (new File(outPath).isDirectory()) {
+                    continue;
+                }
+                writeFile(outPath, zipInputStream);
+                zipInputStream.closeEntry();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new CustomException("压缩包处理异常,异常信息{}"+ e);
+        }
+    }
+
+    //将流写到文件中
+    public static void writeFile(String filePath, ZipInputStream zipInputStream) {
+        File file = new File(filePath);
+        File directory = file.getParentFile();
+        if (!directory.exists()){
+            directory.mkdirs();
+        }
+        try (OutputStream outputStream = new FileOutputStream(filePath)) {
+            byte[] bytes = new byte[4096];
+            int len;
+            while ((len = zipInputStream.read(bytes)) != -1) {
+                outputStream.write(bytes, 0, len);
+            }
+        } catch (IOException ex) {
+            ex.printStackTrace();
+            throw new CustomException("解压文件时,写出到文件出错!");
+        }
+    }
+
+
+    /**
+     * 打包压缩文件夹
+     *
+     * @param folderPath 文件夹路径
+     * @param zipFilePath 压缩后的文件路径
+     * @throws IOException IO异常
+     */
+    public static void zipFolder(String folderPath, String zipFilePath) throws IOException {
+        FileOutputStream fos = null;
+        ZipOutputStream zos = null;
+        try {
+            fos = new FileOutputStream(zipFilePath);
+            zos = new ZipOutputStream(fos);
+
+            // 递归遍历整个文件夹并添加到压缩包
+            addFolderToZip("", new File(folderPath), zos);
+        } finally {
+            if (zos != null) {
+                zos.close();
+            }
+            if (fos != null) {
+                fos.close();
+            }
+        }
+    }
+
+    /**
+     * 将文件夹及其中的文件递归添加到压缩流中
+     *
+     * @param parentPath 父级路径
+     * @param folder 文件夹
+     * @param zos Zip输出流
+     * @throws FileNotFoundException 文件未找到异常
+     * @throws IOException IO异常
+     */
+    private static void addFolderToZip(String parentPath, File folder, ZipOutputStream zos) throws FileNotFoundException, IOException {
+        for (File file : folder.listFiles()) {
+            if (file.isDirectory()) {
+                // 递归添加子文件夹中的文件
+                addFolderToZip(parentPath + folder.getName() + "/", file, zos);
+            } else {
+                FileInputStream fis = null;
+                try {
+                    fis = new FileInputStream(file);
+
+                    // 新建Zip条目并将输入流加入到Zip包中
+                    ZipEntry zipEntry = new ZipEntry( folder.getName() + "/" + file.getName());
+                    zos.putNextEntry(zipEntry);
+
+                    byte[] bytes = new byte[1024];
+                    int length;
+                    while ((length = fis.read(bytes)) >= 0) {
+                        zos.write(bytes, 0, length);
+                    }
+                } finally {
+                    if (fis != null) {
+                        fis.close();
+                    }
+                }
+            }
+        }
+    }
+
+}

+ 728 - 0
src/main/java/com/zhongzheng/common/util/HttpUtils.java

@@ -0,0 +1,728 @@
+package com.zhongzheng.common.util;
+
+import cn.hutool.core.lang.Validator;
+import com.alibaba.fastjson.JSONObject;
+import org.apache.http.*;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.*;
+import java.io.*;
+import java.net.*;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+
+
+/**
+ * 通用http发送方法
+ *
+ * @author zhongzheng
+ */
+public class HttpUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
+    private static final String UTF8 = "UTF-8";
+    private static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded; charset=UTF-8";
+    private static final CloseableHttpClient httpclient = HttpClients.createDefault();
+
+    /**
+     * 向指定 URL 发送GET方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @return 所代表远程资源的响应结果
+     */
+    public static String sendGet(String url, String param)
+    {
+        return sendGet(url, param, UTF8);
+    }
+
+    /**
+     * 向指定 URL 发送GET方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @param contentType 编码类型
+     * @return 所代表远程资源的响应结果
+     */
+    public static String sendGet(String url, String param, String contentType)
+    {
+        StringBuilder result = new StringBuilder();
+        BufferedReader in = null;
+        try
+        {
+            String urlNameString = url + "?" + param;
+            log.info("sendGet - {}", urlNameString);
+            URL realUrl = new URL(urlNameString);
+            URLConnection connection = realUrl.openConnection();
+            connection.setRequestProperty("accept", "*/*");
+            connection.setRequestProperty("connection", "Keep-Alive");
+            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+            connection.connect();
+            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
+            String line;
+            while ((line = in.readLine()) != null)
+            {
+                result.append(line);
+            }
+        //    log.info("recv - {}", result);
+        }
+        catch (ConnectException e)
+        {
+            log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
+        }
+        catch (SocketTimeoutException e)
+        {
+            log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
+        }
+        catch (IOException e)
+        {
+            log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
+        }
+        catch (Exception e)
+        {
+            log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
+        }
+        finally
+        {
+            try
+            {
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+            catch (Exception ex)
+            {
+                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
+            }
+        }
+        return result.toString();
+    }
+    public static String postFormBody(String url, Map<String, String> paramMap) throws IOException{
+        try{
+            String result = post(url, paramMap, null);
+            return result;
+        }catch (Exception e){
+            return null;
+        }
+
+    }
+
+    public static String postFormBodyHeader(String url, Map<String, String> paramMap, Map<String, String> headersMap) throws IOException{
+        try{
+            String result = postHead(url, paramMap, null,headersMap);
+            return result;
+        }catch (Exception e){
+            return null;
+        }
+
+    }
+
+    public static String sendPost(String url, JSONObject param)
+    {
+        HttpClient client = HttpClients.createDefault();
+        HttpPost post = new HttpPost(url);
+        try {
+            //此处应设定参数的编码格式,不然中文会变乱码
+            StringEntity s = new StringEntity(param.toString(), "UTF-8");
+            s.setContentEncoding("UTF-8");
+            s.setContentType("application/json");
+            post.setEntity(s);
+            post.addHeader("content-type", "application/json");
+            System.out.println("url = " + url);
+            System.out.println("param = " + param);
+            HttpResponse res = client.execute(post);
+            if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                if (res.getEntity().getContentType().getValue().equalsIgnoreCase("image/jpeg")) {
+                    InputStream inputStream = res.getEntity().getContent();
+                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+                    byte[] buffer = new byte[1024];
+                    int len = 0;
+                    while ((len = inputStream.read(buffer)) != -1){
+                        outputStream.write(buffer, 0, len);
+                    }
+                    inputStream.close();
+                    Base64.Encoder encoder1 = Base64.getEncoder();
+                    String encoder = "data:image/jpeg;base64,"
+                            + encoder1.encodeToString(outputStream.toByteArray());
+                    return encoder;
+                }
+                String result = EntityUtils.toString(res.getEntity());// 返回json格式
+                System.out.println("推送成功" + result);
+                return result;
+            } else {
+                System.out.println("推送失败");
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+    public static String sendPostJsonHeader(String url, String json, Map<String, String> headersMap)
+    {
+   //     System.out.println(json);
+        HttpClient client = HttpClients.createDefault();
+        HttpPost post = new HttpPost(url);
+        try {
+            //此处应设定参数的编码格式,不然中文会变乱码
+            StringEntity s = new StringEntity(json, "UTF-8");
+            s.setContentEncoding("UTF-8");
+            s.setContentType("application/json");
+            post.setEntity(s);
+            post.addHeader("content-type", "application/json");
+            headersMap.forEach(post::setHeader);
+            HttpResponse res = client.execute(post);
+            if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                if (res.getEntity().getContentType().getValue().equalsIgnoreCase("image/jpeg")) {
+                    InputStream inputStream = res.getEntity().getContent();
+                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+                    byte[] buffer = new byte[1024];
+                    int len = 0;
+                    while ((len = inputStream.read(buffer)) != -1){
+                        outputStream.write(buffer, 0, len);
+                    }
+                    inputStream.close();
+                    Base64.Encoder encoder1 = Base64.getEncoder();
+                    String encoder = "data:image/jpeg;base64,"
+                            + encoder1.encodeToString(outputStream.toByteArray());
+                    return encoder;
+                }
+                String result = EntityUtils.toString(res.getEntity());// 返回json格式
+                System.out.println("推送成功" + result);
+                return result;
+            } else {
+                System.out.println("推送失败");
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+    public static String sendPostJsonHeaderAsync(String url, String json, Map<String, String> headersMap)
+    {
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5*1000).setConnectTimeout(5*1000).build();
+        HttpClient client =  HttpClients.createDefault();
+        HttpPost post = new HttpPost(url);
+        post.setConfig(requestConfig);
+        try {
+            //此处应设定参数的编码格式,不然中文会变乱码
+            StringEntity s = new StringEntity(json, "UTF-8");
+            s.setContentEncoding("UTF-8");
+            s.setContentType("application/json");
+            post.setEntity(s);
+            post.addHeader("content-type", "application/json");
+            headersMap.forEach(post::setHeader);
+            client.execute(post);
+        } catch (Exception e) {
+            e.printStackTrace();
+//            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+    public static String sendPostHeader(String url, JSONObject param, Map<String, String> headersMap)
+    {
+        HttpClient client = HttpClients.createDefault();
+        HttpPost post = new HttpPost(url);
+        try {
+            //此处应设定参数的编码格式,不然中文会变乱码
+            StringEntity s = new StringEntity(param.toString(), "UTF-8");
+            s.setContentEncoding("UTF-8");
+            s.setContentType("application/json");
+            post.setEntity(s);
+            post.addHeader("content-type", "application/json");
+            headersMap.forEach(post::setHeader);
+            HttpResponse res = client.execute(post);
+            if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                if (res.getEntity().getContentType().getValue().equalsIgnoreCase("image/jpeg")) {
+                    InputStream inputStream = res.getEntity().getContent();
+                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+                    byte[] buffer = new byte[1024];
+                    int len = 0;
+                    while ((len = inputStream.read(buffer)) != -1){
+                        outputStream.write(buffer, 0, len);
+                    }
+                    inputStream.close();
+                    Base64.Encoder encoder1 = Base64.getEncoder();
+                    String encoder = "data:image/jpeg;base64,"
+                            + encoder1.encodeToString(outputStream.toByteArray());
+                    return encoder;
+                }
+                String result = EntityUtils.toString(res.getEntity());// 返回json格式
+                System.out.println("推送成功" + result);
+                return result;
+            } else {
+                System.out.println("推送失败");
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+    /**
+     * 向指定 URL 发送POST方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @return 所代表远程资源的响应结果
+     */
+    public static String sendPost(String url, String param)
+    {
+        System.out.println(param);
+        PrintWriter out = null;
+        BufferedReader in = null;
+        StringBuilder result = new StringBuilder();
+        try
+        {
+            String urlNameString = url;
+            log.info("sendPost - {}", urlNameString);
+            URL realUrl = new URL(urlNameString);
+            URLConnection conn = realUrl.openConnection();
+            conn.setRequestProperty("accept", "*/*");
+            conn.setRequestProperty("connection", "Keep-Alive");
+            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+            conn.setRequestProperty("Accept-Charset", "utf-8");
+            conn.setRequestProperty("contentType", "application/json;charset=UTF-8");
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            out = new PrintWriter(conn.getOutputStream());
+            out.print(param);
+            out.flush();
+            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
+            String line;
+            while ((line = in.readLine()) != null)
+            {
+                result.append(line);
+            }
+            log.info("recv - {}", result);
+        }
+        catch (ConnectException e)
+        {
+            log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
+        }
+        catch (SocketTimeoutException e)
+        {
+            log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
+        }
+        catch (IOException e)
+        {
+            log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
+        }
+        catch (Exception e)
+        {
+            log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
+        }
+        finally
+        {
+            try
+            {
+                if (out != null)
+                {
+                    out.close();
+                }
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+            catch (IOException ex)
+            {
+                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
+            }
+        }
+        return result.toString();
+    }
+
+    public static String sendSSLPost(String url, String param)
+    {
+        StringBuilder result = new StringBuilder();
+        String urlNameString = url + "?" + param;
+        try
+        {
+            log.info("sendSSLPost - {}", urlNameString);
+            SSLContext sc = SSLContext.getInstance("SSL");
+            sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
+            URL console = new URL(urlNameString);
+            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
+            conn.setRequestProperty("accept", "*/*");
+            conn.setRequestProperty("connection", "Keep-Alive");
+            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+            conn.setRequestProperty("Accept-Charset", "utf-8");
+            conn.setRequestProperty("contentType", "utf-8");
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+
+            conn.setSSLSocketFactory(sc.getSocketFactory());
+            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
+            conn.connect();
+            InputStream is = conn.getInputStream();
+            BufferedReader br = new BufferedReader(new InputStreamReader(is));
+            String ret = "";
+            while ((ret = br.readLine()) != null)
+            {
+                if (ret != null && !"".equals(ret.trim()))
+                {
+                    result.append(new String(ret.getBytes("ISO-8859-1"), "utf-8"));
+                }
+            }
+            log.info("recv - {}", result);
+            conn.disconnect();
+            br.close();
+        }
+        catch (ConnectException e)
+        {
+            log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
+        }
+        catch (SocketTimeoutException e)
+        {
+            log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
+        }
+        catch (IOException e)
+        {
+            log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
+        }
+        catch (Exception e)
+        {
+            log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
+        }
+        return result.toString();
+    }
+
+    private static class TrustAnyTrustManager implements X509TrustManager
+    {
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType)
+        {
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType)
+        {
+        }
+
+        @Override
+        public X509Certificate[] getAcceptedIssuers()
+        {
+            return new X509Certificate[] {};
+        }
+    }
+
+    private static class TrustAnyHostnameVerifier implements HostnameVerifier
+    {
+        @Override
+        public boolean verify(String hostname, SSLSession session)
+        {
+            return true;
+        }
+    }
+
+    /**
+     * 公共数据解析接口
+     * @param <T>
+     */
+    private interface DataParse<T> {
+        /**
+         * 解析返回数据
+         * @param httpEntity 返回实体
+         * @param encoding 编码
+         * @return 实际解析返回内容
+         * @throws IOException io异常
+         */
+        T parseData(HttpEntity httpEntity, String encoding) throws IOException;
+
+    }
+
+    /**
+     * 将url与map拼接成HTTP查询字符串
+     * @param url 请求url
+     * @param paramMap 需要拼装的map
+     * @return 拼装好的url
+     */
+    public static String appendUrl(String url, Map<String, String> paramMap) throws UnsupportedEncodingException {
+        if (paramMap == null) {
+            return url;
+        }
+        StringBuffer paramStringBuffer = new StringBuffer();
+        Iterator<Map.Entry<String, String>> mapIterator = paramMap.entrySet().iterator();
+        while (mapIterator.hasNext()) {
+            Map.Entry<String, String> next = mapIterator.next();
+            paramStringBuffer.append(next.getKey()).append("=").append(URLEncoder.encode(next.getValue(), UTF8)).append("&");
+        }
+        String paramStr = paramStringBuffer.toString();
+//        String paramStr = mapJoinNotEncode(paramMap);
+        if (paramStr != null && !"".equals(paramStr)) {
+            if (url.indexOf("?") > 0) {
+                if (url.endsWith("&")) {
+                    url += paramStr.substring(0, paramStr.length() - 1);
+                } else {
+                    url += "&" + paramStr.substring(0, paramStr.length() - 1);
+                }
+            } else {
+                url += "?" + paramStr.substring(0, paramStr.length() - 1);
+            }
+        }
+        return url;
+    }
+
+    private static String postHead(String url, Map<String, String> paramMap, String encoding, Map<String, String> headersMap) throws IOException, NoSuchAlgorithmException, KeyManagementException {
+        log.debug("http 请求 url: {} , 请求参数: {}", url, appendUrl("", paramMap).replace("?", ""));
+        encoding = encoding == null ? UTF8 : encoding;
+        // 创建post方式请求对象
+        HttpPost httpPost = new HttpPost(url);
+        // 装填参数
+        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
+        if (paramMap != null) {
+            for (Map.Entry<String, String> entry : paramMap.entrySet()) {
+                String value = entry.getValue();
+                //去掉如下判断会造成String类型的value为null时
+                if (value != null) {
+                    nameValuePairs.add(new BasicNameValuePair(entry.getKey(), value));
+                }
+            }
+        }
+        // 设置参数到请求对象中
+        httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, encoding));
+        // 设置header信息
+        // 指定报文头【Content-type】、【User-Agent】
+        httpPost.setHeader("Content-type", APPLICATION_FORM_URLENCODED);
+        headersMap.forEach(httpPost::setHeader);
+        return post(url, httpPost, encoding, new DataParse<String>() {
+            @Override
+            public String parseData(HttpEntity httpEntity, String encoding) throws IOException {
+                if(Validator.isNotEmpty(httpEntity)){
+                    return EntityUtils.toString(httpEntity, encoding);
+                }else{
+                    return null;
+                }
+
+            }
+        });
+    }
+
+    /**
+     * 向url发送post请求表单提交数据
+     * @param url 请求url
+     * @param paramMap 表单数据
+     * @param encoding 编码
+     * @return 请求返回的数据
+     * @throws IOException 读写异常
+     */
+    private static String post(String url, Map<String, String> paramMap, String encoding) throws IOException, NoSuchAlgorithmException, KeyManagementException {
+        log.debug("http 请求 url: {} , 请求参数: {}", url, appendUrl("", paramMap).replace("?", ""));
+        encoding = encoding == null ? UTF8 : encoding;
+        // 创建post方式请求对象
+        HttpPost httpPost = new HttpPost(url);
+        // 装填参数
+        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
+        if (paramMap != null) {
+            for (Map.Entry<String, String> entry : paramMap.entrySet()) {
+                String value = entry.getValue();
+                //去掉如下判断会造成String类型的value为null时
+                if (value != null) {
+                    nameValuePairs.add(new BasicNameValuePair(entry.getKey(), value));
+                }
+            }
+        }
+        // 设置参数到请求对象中
+        httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, encoding));
+        // 设置header信息
+        // 指定报文头【Content-type】、【User-Agent】
+        httpPost.setHeader("Content-type", APPLICATION_FORM_URLENCODED);
+        return post(url, httpPost, encoding, new DataParse<String>() {
+            @Override
+            public String parseData(HttpEntity httpEntity, String encoding) throws IOException {
+                if(Validator.isNotEmpty(httpEntity)){
+                    return EntityUtils.toString(httpEntity, encoding);
+                }else{
+                    return null;
+                }
+
+            }
+        });
+    }
+
+    /**
+     * 向url发送post请求
+     * @param url 请求url
+     * @param httpPost httpClient
+     * @return 请求返回的数据
+     * @throws IOException 读写异常
+     */
+    private static <T> T post(String url, HttpPost httpPost, String encoding, DataParse<T> dataParse)
+            throws IOException, NoSuchAlgorithmException, KeyManagementException {
+        TrustManager[] trustAllCerts = new TrustManager[] {
+                new X509TrustManager() {
+                    public X509Certificate[] getAcceptedIssuers() {
+                        return null;
+                    }
+                    public void checkClientTrusted(X509Certificate[] certs, String authType) {
+                        // don't check
+                    }
+                    public void checkServerTrusted(X509Certificate[] certs, String authType) {
+                        // don't check
+                    }
+                }
+        };
+
+        SSLContext ctx = SSLContext.getInstance("TLS");
+        ctx.init(null, trustAllCerts, null);
+        LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(ctx);
+        T result = null;
+        CloseableHttpResponse response = null;
+        // 创建httpclient对象
+        CloseableHttpClient httpClient = HttpClientBuilder.create().setSSLSocketFactory(sslSocketFactory).build();
+
+        // 执行请求操作,并拿到结果(同步阻塞)
+        response = sendRequestAndGetResult(url, httpClient, httpPost);
+        // 获取结果实体
+        // 判断网络连接状态码是否正常(0--200都数正常)
+        if (null != response) {
+            result = dataParse.parseData(response.getEntity(), encoding);
+            log.debug("http 请求结果: {}", result);
+        }
+        try {
+            if (null != response) {
+                response.close();
+            }
+        } catch (IOException ex) {
+            log.error(ex.getMessage(), ex);
+        }
+        return result;
+    }
+
+    /**
+     * 设置http头,发送http请求,打印请求耗时
+     * @param url 请求url
+     * @param httpClient httpClient
+     * @param httpUriRequest httpUriRequest
+     * @return 请求返回的数据
+     * @throws IOException 读写异常
+     */
+    private static CloseableHttpResponse sendRequestAndGetResult(String url, CloseableHttpClient httpClient,
+                                                                 HttpUriRequest httpUriRequest) throws IOException {
+        long startTime = System.currentTimeMillis();
+        CloseableHttpResponse response = httpClient.execute(httpUriRequest);
+        long endTime = System.currentTimeMillis();
+        collectAPISpendTime(url, startTime, endTime);
+        return response;
+    }
+
+    /**
+     * 打印请求信息
+     * @param url 请求url
+     * @param startTime 请求开始时间
+     * @param endTime 请求结束时间
+     */
+    private static void collectAPISpendTime(String url, long startTime, long endTime) {
+        log.debug("HTTP请求耗时分析,请求URL: {} , 耗时: {} ms", url, endTime - startTime);
+    }
+
+    /**
+     * 向指定 URL 发送POST方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @param headersMap 请求头
+     * @return 所代表远程资源的响应结果
+     */
+    public static CloseableHttpResponse sendPostHeader(String url, String param, Map<String, String> headersMap)
+    {
+        // 字符串编码
+        StringEntity entity = new StringEntity(param, Consts.UTF_8);
+        // 设置content-type
+        entity.setContentType("application/json");
+        HttpPost httpPost = new HttpPost(url);
+        // 防止被当成攻击添加的
+        httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1");
+        // 接收参数设置
+        httpPost.setHeader("connection", "Keep-Alive");
+        httpPost.setHeader("Accept", "application/json");
+        httpPost.setHeader("Accept-Charset", "utf-8");
+        httpPost.setHeader("contentType", "utf-8");
+        headersMap.forEach(httpPost::setHeader);
+        httpPost.setEntity(entity);
+        CloseableHttpResponse response = null;
+        try {
+            response = httpclient.execute(httpPost);
+            Header[] headers = response.getHeaders("Set-Cookie");
+            Arrays.stream(headers).forEach(httpPost::addHeader);
+        } catch (IOException e) {
+            log.error(e.getMessage());
+        }
+        return response;
+    }
+
+    public static String postFormBodyHeader(String url, Map<String, String> paramMap, Map<String, String> headersMap, Header[] header) throws IOException{
+        try{
+            String result = postHearder(url, paramMap, null, headersMap, header);
+            return result;
+        }catch (Exception e){
+            return null;
+        }
+    }
+
+    /**
+     * 向url发送post请求表单提交数据
+     * @param url 请求url
+     * @param paramMap 表单数据
+     * @param encoding 编码
+     * @param headersMap 请求头
+     * @return 请求返回的数据
+     * @throws IOException 读写异常
+     */
+    private static String postHearder(String url, Map<String, String> paramMap, String encoding, Map<String, String> headersMap, Header[] header) throws IOException, NoSuchAlgorithmException, KeyManagementException {
+        log.debug("http 请求 url: {} , 请求参数: {}", url, appendUrl("", paramMap).replace("?", ""));
+        encoding = encoding == null ? UTF8 : encoding;
+        // 创建post方式请求对象
+        HttpPost httpPost = new HttpPost(url);
+        // 装填参数
+        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
+        if (paramMap != null) {
+            for (Map.Entry<String, String> entry : paramMap.entrySet()) {
+                String value = entry.getValue();
+                //去掉如下判断会造成String类型的value为null时
+                if (value != null) {
+                    nameValuePairs.add(new BasicNameValuePair(entry.getKey(), value));
+                }
+            }
+        }
+        // 设置参数到请求对象中
+        httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, encoding));
+        // 设置header信息
+        // 指定报文头【Content-type】、【User-Agent】
+        httpPost.setHeader("Content-type", APPLICATION_FORM_URLENCODED);
+        httpPost.setHeader("Accept", "*/*");
+        httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1");
+        headersMap.forEach(httpPost::setHeader);
+        Arrays.stream(header).forEach(head->{
+            httpPost.addHeader("Cookie", head.getValue());
+        });
+        return post(url, httpPost, encoding, new DataParse<String>() {
+            @Override
+            public String parseData(HttpEntity httpEntity, String encoding) throws IOException {
+                return EntityUtils.toString(httpEntity, encoding);
+            }
+        });
+    }
+
+}

+ 131 - 0
src/main/java/com/zhongzheng/common/util/ImageUtils.java

@@ -0,0 +1,131 @@
+package com.zhongzheng.common.util;
+
+import cn.hutool.core.util.StrUtil;
+import com.zhongzheng.common.config.RuoYiConfig;
+import com.zhongzheng.common.filter.Constants;
+import org.apache.poi.util.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sun.misc.BASE64Encoder;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+
+/**
+ * 图片处理工具类
+ *
+ * @author zhongzheng
+ */
+public class ImageUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);
+
+    public static byte[] getImage(String imagePath)
+    {
+        InputStream is = getFile(imagePath);
+        try
+        {
+            return IOUtils.toByteArray(is);
+        }
+        catch (Exception e)
+        {
+            log.error("图片加载异常 {}", e);
+            return null;
+        }
+        finally
+        {
+            IOUtils.closeQuietly(is);
+        }
+    }
+
+    public static InputStream getFile(String imagePath)
+    {
+        try
+        {
+            byte[] result = readFile(imagePath);
+            result = Arrays.copyOf(result, result.length);
+            return new ByteArrayInputStream(result);
+        }
+        catch (Exception e)
+        {
+            log.error("获取图片异常 {}", e);
+        }
+        return null;
+    }
+
+    /**
+     * 读取文件为字节数据
+     *
+     * @param key 地址
+     * @return 字节数据
+     */
+    public static byte[] readFile(String url)
+    {
+        InputStream in = null;
+        ByteArrayOutputStream baos = null;
+        try
+        {
+            if (url.startsWith("http"))
+            {
+                // 网络地址
+                URL urlObj = new URL(url);
+                URLConnection urlConnection = urlObj.openConnection();
+                urlConnection.setConnectTimeout(30 * 1000);
+                urlConnection.setReadTimeout(60 * 1000);
+                urlConnection.setDoInput(true);
+                in = urlConnection.getInputStream();
+            }
+            else
+            {
+                // 本机地址
+                String localPath = RuoYiConfig.getProfile();
+                String downloadPath = localPath + StrUtil.subAfter(url, Constants.RESOURCE_PREFIX,false);
+                in = new FileInputStream(downloadPath);
+            }
+            return IOUtils.toByteArray(in);
+        }
+        catch (Exception e)
+        {
+            log.error("获取文件路径异常 {}", e);
+            return null;
+        }
+        finally
+        {
+            IOUtils.closeQuietly(baos);
+        }
+    }
+
+
+    public static String encodeImgageToBase64(String url) {
+        BASE64Encoder encoder = new BASE64Encoder();
+        String s= encoder.encode(readFile(url));
+        return s;
+
+    }
+
+    /** * 旋转图片为指定角度  图片宽高不变*
+     * @param bufferedimage * 目标图像 *
+     * @param degree * 旋转角度 *
+     * @return */
+    public static BufferedImage rotateImage(final BufferedImage bufferedimage, final int degree)
+    {
+        int w= bufferedimage.getWidth(); // 得到图片宽度。
+        int h= bufferedimage.getHeight();// 得到图片高度。
+        int type= bufferedimage.getColorModel().getTransparency();// 得到图片透明度。
+        BufferedImage img;// 空的图片。
+        Graphics2D graphics2d;// 空的画笔。
+        (graphics2d= (img= new BufferedImage(w, h, type)).createGraphics()).setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+        graphics2d.rotate(Math.toRadians(degree), w/2, h/2);// 旋转,degree是整型,度数,比如垂直90度。   •rotate(double arc,double x, double y):图形以点(x,y)为轴点,旋转arc弧度。
+        graphics2d.drawImage(bufferedimage, 0, 0, null);// 从bufferedimagecopy图片至img,0,0是img的坐标。
+        graphics2d.dispose();
+
+        return img;// 返回复制好的图片,原图片依然没有变,没有旋转,下次还可以使用。
+    }
+}

+ 196 - 0
src/main/java/com/zhongzheng/common/util/IpUtils.java

@@ -0,0 +1,196 @@
+package com.zhongzheng.common.util;
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.http.HtmlUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * 获取IP方法
+ * 
+ * @author zhongzheng
+ */
+public class IpUtils
+{
+    public static String getIpAddr(HttpServletRequest request)
+    {
+        if (request == null)
+        {
+            return "unknown";
+        }
+        String ip = request.getHeader("x-forwarded-for");
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("X-Forwarded-For");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("X-Real-IP");
+        }
+
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getRemoteAddr();
+        }
+        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
+    }
+
+    public static boolean internalIp(String ip)
+    {
+        byte[] addr = textToNumericFormatV4(ip);
+        return internalIp(addr) || "127.0.0.1".equals(ip);
+    }
+
+    private static boolean internalIp(byte[] addr)
+    {
+        if (Validator.isNull(addr) || addr.length < 2)
+        {
+            return true;
+        }
+        final byte b0 = addr[0];
+        final byte b1 = addr[1];
+        // 10.x.x.x/8
+        final byte SECTION_1 = 0x0A;
+        // 172.16.x.x/12
+        final byte SECTION_2 = (byte) 0xAC;
+        final byte SECTION_3 = (byte) 0x10;
+        final byte SECTION_4 = (byte) 0x1F;
+        // 192.168.x.x/16
+        final byte SECTION_5 = (byte) 0xC0;
+        final byte SECTION_6 = (byte) 0xA8;
+        switch (b0)
+        {
+            case SECTION_1:
+                return true;
+            case SECTION_2:
+                if (b1 >= SECTION_3 && b1 <= SECTION_4)
+                {
+                    return true;
+                }
+            case SECTION_5:
+                switch (b1)
+                {
+                    case SECTION_6:
+                        return true;
+                }
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * 将IPv4地址转换成字节
+     * 
+     * @param text IPv4地址
+     * @return byte 字节
+     */
+    public static byte[] textToNumericFormatV4(String text)
+    {
+        if (text.length() == 0)
+        {
+            return null;
+        }
+
+        byte[] bytes = new byte[4];
+        String[] elements = text.split("\\.", -1);
+        try
+        {
+            long l;
+            int i;
+            switch (elements.length)
+            {
+                case 1:
+                    l = Long.parseLong(elements[0]);
+                    if ((l < 0L) || (l > 4294967295L)) {
+                        return null;
+                    }
+                    bytes[0] = (byte) (int) (l >> 24 & 0xFF);
+                    bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
+                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 2:
+                    l = Integer.parseInt(elements[0]);
+                    if ((l < 0L) || (l > 255L)) {
+                        return null;
+                    }
+                    bytes[0] = (byte) (int) (l & 0xFF);
+                    l = Integer.parseInt(elements[1]);
+                    if ((l < 0L) || (l > 16777215L)) {
+                        return null;
+                    }
+                    bytes[1] = (byte) (int) (l >> 16 & 0xFF);
+                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 3:
+                    for (i = 0; i < 2; ++i)
+                    {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L)) {
+                            return null;
+                        }
+                        bytes[i] = (byte) (int) (l & 0xFF);
+                    }
+                    l = Integer.parseInt(elements[2]);
+                    if ((l < 0L) || (l > 65535L)) {
+                        return null;
+                    }
+                    bytes[2] = (byte) (int) (l >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 4:
+                    for (i = 0; i < 4; ++i)
+                    {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L)) {
+                            return null;
+                        }
+                        bytes[i] = (byte) (int) (l & 0xFF);
+                    }
+                    break;
+                default:
+                    return null;
+            }
+        }
+        catch (NumberFormatException e)
+        {
+            return null;
+        }
+        return bytes;
+    }
+
+    public static String getHostIp()
+    {
+        try
+        {
+            return InetAddress.getLocalHost().getHostAddress();
+        }
+        catch (UnknownHostException e)
+        {
+        }
+        return "127.0.0.1";
+    }
+
+    public static String getHostName()
+    {
+        try
+        {
+            return InetAddress.getLocalHost().getHostName();
+        }
+        catch (UnknownHostException e)
+        {
+        }
+        return "未知";
+    }
+}

+ 46 - 0
src/main/java/com/zhongzheng/common/util/JavaMailUtils.java

@@ -0,0 +1,46 @@
+package com.zhongzheng.common.util;
+
+
+import javax.mail.Authenticator;
+import javax.mail.PasswordAuthentication;
+import javax.mail.Session;
+import java.util.Properties;
+
+/**
+ * @author yangdamao
+ * @date 2023年06月19日 10:11
+ */
+public class JavaMailUtils {
+
+    public static Session createsession(String postAccount,String postPassword,String STMPserver,String post) {
+        // SMTP服务器地址
+        String smtp = STMPserver;
+
+        // 邮箱账号和密码(授权密码)
+        String userName = postAccount;
+        String password = postPassword;
+
+        // SMTP服务器的连接信息
+        Properties props = new Properties();
+        props.put("mail.smtp.host", smtp); // SMTP主机号
+        props.put("mail.smtp.port", post); // 主机端口号
+        props.put("mail.smtp.auth", "true"); // 是否需要认证
+        props.put("mail.smtp.starttls.enable", "true"); // 启用TLS加密
+        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
+
+        // 创建Session
+        // 参数1:SMTP服务器的连接信息
+        // 参数2:用户认证对象(Authenticator接口的匿名实现类)
+        Session session = Session.getInstance(props, new Authenticator() {
+            @Override
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(userName, password);
+            }
+        });
+
+        // 开启调试模式
+        session.setDebug(true);
+
+        return session;
+    }
+}

+ 25 - 0
src/main/java/com/zhongzheng/common/util/MessageUtils.java

@@ -0,0 +1,25 @@
+package com.zhongzheng.common.util;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.i18n.LocaleContextHolder;
+
+/**
+ * 获取i18n资源文件
+ * 
+ * @author zhongzheng
+ */
+public class MessageUtils
+{
+    /**
+     * 根据消息键和参数 获取消息 委托给spring messageSource
+     *
+     * @param code 消息键
+     * @param args 参数
+     * @return 获取国际化翻译值
+     */
+    public static String message(String code, Object... args)
+    {
+        MessageSource messageSource = SpringUtils.getBean(MessageSource.class);
+        return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
+    }
+}

+ 55 - 0
src/main/java/com/zhongzheng/common/util/MimeTypeUtils.java

@@ -0,0 +1,55 @@
+package com.zhongzheng.common.util;
+
+/**
+ * 媒体类型工具类
+ * 
+ * @author zhongzheng
+ */
+public class MimeTypeUtils
+{
+    public static final String IMAGE_PNG = "image/png";
+
+    public static final String IMAGE_JPG = "image/jpg";
+
+    public static final String IMAGE_JPEG = "image/jpeg";
+
+    public static final String IMAGE_BMP = "image/bmp";
+
+    public static final String IMAGE_GIF = "image/gif";
+    
+    public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };
+
+    public static final String[] FLASH_EXTENSION = { "swf", "flv" };
+
+    public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
+            "asf", "rm", "rmvb" };
+
+    public static final String[] DEFAULT_ALLOWED_EXTENSION = {
+            // 图片
+            "bmp", "gif", "jpg", "jpeg", "png",
+            // word excel powerpoint
+            "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
+            // 压缩文件
+            "rar", "zip", "gz", "bz2",
+            // pdf
+            "pdf" };
+
+    public static String getExtension(String prefix)
+    {
+        switch (prefix)
+        {
+            case IMAGE_PNG:
+                return "png";
+            case IMAGE_JPG:
+                return "jpg";
+            case IMAGE_JPEG:
+                return "jpeg";
+            case IMAGE_BMP:
+                return "bmp";
+            case IMAGE_GIF:
+                return "gif";
+            default:
+                return "";
+        }
+    }
+}

+ 405 - 0
src/main/java/com/zhongzheng/common/util/ReflectUtils.java

@@ -0,0 +1,405 @@
+package com.zhongzheng.common.util;
+
+import cn.hutool.core.convert.Convert;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.*;
+import java.util.Date;
+
+/**
+ * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
+ * 
+ * @author zhongzheng
+ */
+@SuppressWarnings("rawtypes")
+public class ReflectUtils
+{
+    private static final String SETTER_PREFIX = "set";
+
+    private static final String GETTER_PREFIX = "get";
+
+    private static final String CGLIB_CLASS_SEPARATOR = "$$";
+
+    private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class);
+
+    /**
+     * 调用Getter方法.
+     * 支持多级,如:对象名.对象名.方法
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E 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 (E) object;
+    }
+
+    /**
+     * 调用Setter方法, 仅匹配方法名。
+     * 支持多级,如:对象名.对象名.方法
+     */
+    public static <E> void invokeSetter(Object obj, String propertyName, E 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函数.
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E getFieldValue(final Object obj, final String fieldName)
+    {
+        Field field = getAccessibleField(obj, fieldName);
+        if (field == null)
+        {
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
+            return null;
+        }
+        E result = null;
+        try
+        {
+            result = (E) field.get(obj);
+        }
+        catch (IllegalAccessException e)
+        {
+            logger.error("不可能抛出的异常{}", e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
+     */
+    public static <E> void setFieldValue(final Object obj, final String fieldName, final E value)
+    {
+        Field field = getAccessibleField(obj, fieldName);
+        if (field == null)
+        {
+            // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
+            return;
+        }
+        try
+        {
+            field.set(obj, value);
+        }
+        catch (IllegalAccessException e)
+        {
+            logger.error("不可能抛出的异常: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 直接调用对象方法, 无视private/protected修饰符.
+     * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用.
+     * 同时匹配方法名+参数类型,
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
+            final Object[] args)
+    {
+        if (obj == null || methodName == null)
+        {
+            return null;
+        }
+        Method method = getAccessibleMethod(obj, methodName, parameterTypes);
+        if (method == null)
+        {
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
+            return null;
+        }
+        try
+        {
+            return (E) method.invoke(obj, args);
+        }
+        catch (Exception e)
+        {
+            String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
+            throw convertReflectionExceptionToUnchecked(msg, e);
+        }
+    }
+
+    /**
+     * 直接调用对象方法, 无视private/protected修饰符,
+     * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
+     * 只匹配函数名,如果有多个同名函数调用第一个。
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E invokeMethodByName(final Object obj, final String methodName, final Object[] args)
+    {
+        Method method = getAccessibleMethodByName(obj, methodName, args.length);
+        if (method == null)
+        {
+            // 如果为空不报错,直接返回空。
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
+            return null;
+        }
+        try
+        {
+            // 类型转换(将参数数据类型转换为目标方法参数类型)
+            Class<?>[] cs = method.getParameterTypes();
+            for (int i = 0; i < cs.length; i++)
+            {
+                if (args[i] != null && !args[i].getClass().equals(cs[i]))
+                {
+                    if (cs[i] == String.class)
+                    {
+                        args[i] = Convert.toStr(args[i]);
+                        if (StringUtils.endsWith((String) args[i], ".0"))
+                        {
+                            args[i] = StringUtils.substringBefore((String) args[i], ".0");
+                        }
+                    }
+                    else if (cs[i] == Integer.class)
+                    {
+                        args[i] = Convert.toInt(args[i]);
+                    }
+                    else if (cs[i] == Long.class)
+                    {
+                        args[i] = Convert.toLong(args[i]);
+                    }
+                    else if (cs[i] == Double.class)
+                    {
+                        args[i] = Convert.toDouble(args[i]);
+                    }
+                    else if (cs[i] == Float.class)
+                    {
+                        args[i] = Convert.toFloat(args[i]);
+                    }
+                    else if (cs[i] == Date.class)
+                    {
+                        if (args[i] instanceof String)
+                        {
+                            args[i] = DateUtils.parseDate(args[i]);
+                        }
+                        else
+                        {
+                            args[i] = DateUtil.getJavaDate((Double) args[i]);
+                        }
+                    }
+                    else if (cs[i] == boolean.class || cs[i] == Boolean.class)
+                    {
+                        args[i] = Convert.toBool(args[i]);
+                    }
+                }
+            }
+            return (E) method.invoke(obj, args);
+        }
+        catch (Exception e)
+        {
+            String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
+            throw convertReflectionExceptionToUnchecked(msg, e);
+        }
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
+     * 如向上转型到Object仍无法找到, 返回null.
+     */
+    public static Field getAccessibleField(final Object obj, final String fieldName)
+    {
+        // 为空不报错。直接返回 null
+        if (obj == null)
+        {
+            return 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)
+            {
+                continue;
+            }
+        }
+        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)
+    {
+        // 为空不报错。直接返回 null
+        if (obj == null)
+        {
+            return 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)
+            {
+                continue;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
+     * 如向上转型到Object仍无法找到, 返回null.
+     * 只匹配函数名。
+     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
+     */
+    public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum)
+    {
+        // 为空不报错。直接返回 null
+        if (obj == null)
+        {
+            return 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) && method.getParameterTypes().length == argsNum)
+                {
+                    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.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Class<T> getClassGenricType(final Class clazz)
+    {
+        return getClassGenricType(clazz, 0);
+    }
+
+    /**
+     * 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
+     * 如无法找到, 返回Object.class.
+     */
+    public static Class getClassGenricType(final Class clazz, final int index)
+    {
+        Type genType = clazz.getGenericSuperclass();
+
+        if (!(genType instanceof ParameterizedType))
+        {
+            logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType");
+            return Object.class;
+        }
+
+        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
+
+        if (index >= params.length || index < 0)
+        {
+            logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
+                    + params.length);
+            return Object.class;
+        }
+        if (!(params[index] instanceof Class))
+        {
+            logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
+            return Object.class;
+        }
+
+        return (Class) params[index];
+    }
+
+    public static Class<?> getUserClass(Object instance)
+    {
+        if (instance == null)
+        {
+            throw new RuntimeException("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(String msg, Exception e)
+    {
+        if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
+                || e instanceof NoSuchMethodException)
+        {
+            return new IllegalArgumentException(msg, e);
+        }
+        else if (e instanceof InvocationTargetException)
+        {
+            return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException());
+        }
+        return new RuntimeException(msg, e);
+    }
+}

+ 110 - 0
src/main/java/com/zhongzheng/common/util/SecurityUtils.java

@@ -0,0 +1,110 @@
+package com.zhongzheng.common.util;
+
+import cn.hutool.http.HttpStatus;
+import com.zhongzheng.common.croe.LoginUser;
+import com.zhongzheng.common.filter.CustomException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * 安全服务工具类
+ *
+ * @author zhongzheng
+ */
+@Component
+public class SecurityUtils
+{
+
+   // @Value("${mybatis-plus.tenant.enabled-tenant:true}")
+    public static boolean EnabledTenant;
+
+    @Value("${mybatis-plus.tenant.enabled-tenant:true}")
+    private boolean enabledTenant;
+
+    @PostConstruct
+    public void getEnvironment(){
+        EnabledTenant = this.enabledTenant;
+    }
+    /**
+     * 获取用户账户
+     **/
+    public static String getUsername()
+    {
+        try
+        {
+            return getLoginUser().getUsername();
+        }
+        catch (Exception e)
+        {
+            throw new CustomException("获取用户账户异常", HttpStatus.HTTP_UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取用户
+     **/
+    public static LoginUser getLoginUser()
+    {
+        try
+        {
+            return (LoginUser) getAuthentication().getPrincipal();
+        }
+        catch (Exception e)
+        {
+            throw new CustomException("获取用户信息异常", HttpStatus.HTTP_UNAUTHORIZED);
+        }
+    }
+
+
+    /**
+     * 获取Authentication
+     */
+    public static Authentication getAuthentication()
+    {
+        return SecurityContextHolder.getContext().getAuthentication();
+    }
+
+    /**
+     * 生成BCryptPasswordEncoder密码
+     *
+     * @param password 密码
+     * @return 加密字符串
+     */
+    public static String encryptPassword(String password)
+    {
+        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.encode(password);
+    }
+
+    /**
+     * 判断密码是否相同
+     *
+     * @param rawPassword 真实密码
+     * @param encodedPassword 加密后字符
+     * @return 结果
+     */
+    public static boolean matchesPassword(String rawPassword, String encodedPassword)
+    {
+        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.matches(rawPassword, encodedPassword);
+    }
+
+    /**
+     * 是否为管理员
+     *
+     * @param userId 用户ID
+     * @return 结果
+     */
+    public static boolean isAdmin(Long userId)
+    {
+        LoginUser user= (LoginUser) getAuthentication().getPrincipal();
+        return user != null && "admin".equals(user.getUser().getUserName());
+     //   return userId != null && 1L == userId;
+    }
+
+}

+ 159 - 0
src/main/java/com/zhongzheng/common/util/ServletUtils.java

@@ -0,0 +1,159 @@
+package com.zhongzheng.common.util;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+
+/**
+ * 客户端工具类
+ *
+ * @author zhongzheng
+ */
+public class ServletUtils
+{
+    public static HttpServletRequest httpServletRequest;
+    /**
+     * 获取模块编码参数
+     */
+    public static String getEncoded(String tag)
+    {
+        String time = String.valueOf(System.currentTimeMillis());
+        return tag+Long.valueOf(time.substring(1));
+    }
+    //导入生成编号使用
+    public static String getImportEncoded(String tag)
+    {
+        String time = String.valueOf(System.currentTimeMillis()/1000);
+        return tag+Integer.valueOf(time.substring(2))+(int)((Math.random()*100));
+    }
+    /**
+     * 获取String参数
+     */
+    public static String getParameter(String name)
+    {
+        return getRequest().getParameter(name);
+    }
+
+    /**
+     * 获取String参数
+     */
+    public static String getParameter(String name, String defaultValue)
+    {
+        return Convert.toStr(getRequest().getParameter(name), defaultValue);
+    }
+
+    /**
+     * 获取Integer参数
+     */
+    public static Integer getParameterToInt(String name)
+    {
+        return Convert.toInt(getRequest().getParameter(name));
+    }
+
+    /**
+     * 获取Integer参数
+     */
+    public static Integer getParameterToInt(String name, Integer defaultValue)
+    {
+        return Convert.toInt(getRequest().getParameter(name), defaultValue);
+    }
+
+    /**
+     * 获取request
+     */
+    public static HttpServletRequest getRequest()
+    {
+        if(getRequestAttributes()!=null){
+            return getRequestAttributes().getRequest();
+        }else{
+            return httpServletRequest;
+        }
+
+    }
+
+
+    /**
+     * 获取response
+     */
+    public static HttpServletResponse getResponse()
+    {
+        return getRequestAttributes().getResponse();
+    }
+
+    /**
+     * 获取session
+     */
+    public static HttpSession getSession()
+    {
+        return getRequest().getSession();
+    }
+
+    public static ServletRequestAttributes getRequestAttributes()
+    {
+        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+        return (ServletRequestAttributes) attributes;
+    }
+
+    /**
+     * 将字符串渲染到客户端
+     *
+     * @param response 渲染对象
+     * @param string 待渲染的字符串
+     * @return null
+     */
+    public static String renderString(HttpServletResponse response, String string)
+    {
+        try
+        {
+            response.setStatus(200);
+            response.setContentType("application/json");
+            response.setCharacterEncoding("utf-8");
+            response.getWriter().print(string);
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 是否是Ajax异步请求
+     *
+     * @param request
+     */
+    public static boolean isAjaxRequest(HttpServletRequest request)
+    {
+        String accept = request.getHeader("accept");
+        if (accept != null && accept.indexOf("application/json") != -1)
+        {
+            return true;
+        }
+
+        String xRequestedWith = request.getHeader("X-Requested-With");
+        if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1)
+        {
+            return true;
+        }
+
+        String uri = request.getRequestURI();
+        if (StrUtil.equalsAnyIgnoreCase(uri, ".json", ".xml"))
+        {
+            return true;
+        }
+
+        String ajax = request.getParameter("__ajax");
+        if (StrUtil.equalsAnyIgnoreCase(ajax, "json", "xml"))
+        {
+            return true;
+        }
+        return false;
+    }
+}

+ 52 - 0
src/main/java/com/zhongzheng/common/util/SmsUtils.java

@@ -0,0 +1,52 @@
+package com.zhongzheng.common.util;
+
+import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
+import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
+import com.aliyun.teaopenapi.models.Config;
+
+public class SmsUtils {
+    //产品名称:云通信短信API产品,开发者无需替换
+    static final String product = "Dysmsapi";
+    //产品域名,开发者无需替换
+    static final String domain = "dysmsapi.aliyuncs.com";
+
+    // TODO 此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)
+    static final String accessKeyId = "yourAccessKeyId";
+    static final String accessKeySecret = "yourAccessKeySecret";
+
+    public static com.aliyun.dysmsapi20170525.Client createClient(String accessKeyId, String accessKeySecret) throws Exception {
+        Config config = new Config()
+                // 您的AccessKey ID
+                .setAccessKeyId(accessKeyId)
+                // 您的AccessKey Secret
+                .setAccessKeySecret(accessKeySecret);
+        // 访问的域名
+        config.endpoint = "dysmsapi.aliyuncs.com";
+        return new com.aliyun.dysmsapi20170525.Client(config);
+    }
+
+    public static SendSmsResponse sendSms(String tel,String signName,String templateCode,String Param,String accessKeyId,String accessKeySecret) throws  Exception {
+        //可自助调整超时时间
+      /*  System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
+        System.setProperty("sun.net.client.defaultReadTimeout", "10000");*/
+
+        com.aliyun.dysmsapi20170525.Client client = SmsUtils.createClient(accessKeyId, accessKeySecret);
+        SendSmsRequest request = new SendSmsRequest();
+        //必填:待发送手机号
+        request.setPhoneNumbers(tel);
+        //必填:短信签名-可在短信控制台中找到
+        request.setSignName(signName);
+        //必填:短信模板-可在短信控制台中找到
+        request.setTemplateCode(templateCode);
+        //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
+        //"{\"name\":\"Tom\", \"code\":\"123\"}"
+        request.setTemplateParam(Param);
+        //选填-上行短信扩展码(无特殊需求用户请忽略此字段)
+        //request.setSmsUpExtendCode("90997");
+        //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
+    //    request.setOutId("yourOutId");
+        SendSmsResponse sendSmsResponse = client.sendSms(request);
+
+        return sendSmsResponse;
+    }
+}

+ 128 - 0
src/main/java/com/zhongzheng/common/util/SnowflakeIdUtils.java

@@ -0,0 +1,128 @@
+package com.zhongzheng.common.util;
+
+public class SnowflakeIdUtils {
+    // ==============================Fields===========================================
+    /** 开始时间截 (2015-01-01) */
+    private final long twepoch = 1420041600000L;
+
+    /** 机器id所占的位数 */
+    private final long workerIdBits = 5L;
+
+    /** 数据标识id所占的位数 */
+    private final long datacenterIdBits = 5L;
+
+    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
+    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
+
+    /** 支持的最大数据标识id,结果是31 */
+    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
+
+    /** 序列在id中占的位数 */
+    private final long sequenceBits = 12L;
+
+    /** 机器ID向左移12位 */
+    private final long workerIdShift = sequenceBits;
+
+    /** 数据标识id向左移17位(12+5) */
+    private final long datacenterIdShift = sequenceBits + workerIdBits;
+
+    /** 时间截向左移22位(5+5+12) */
+    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
+
+    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
+    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
+
+    /** 工作机器ID(0~31) */
+    private long workerId;
+
+    /** 数据中心ID(0~31) */
+    private long datacenterId;
+
+    /** 毫秒内序列(0~4095) */
+    private long sequence = 0L;
+
+    /** 上次生成ID的时间截 */
+    private long lastTimestamp = -1L;
+
+    //==============================Constructors=====================================
+    /**
+     * 构造函数
+     * @param workerId 工作ID (0~31)
+     * @param datacenterId 数据中心ID (0~31)
+     */
+    public SnowflakeIdUtils(long workerId, long datacenterId) {
+        if (workerId > maxWorkerId || workerId < 0) {
+            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
+        }
+        if (datacenterId > maxDatacenterId || datacenterId < 0) {
+            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
+        }
+        this.workerId = workerId;
+        this.datacenterId = datacenterId;
+    }
+
+    // ==============================Methods==========================================
+    /**
+     * 获得下一个ID (该方法是线程安全的)
+     * @return SnowflakeId
+     */
+    public synchronized long nextId() {
+        long timestamp = timeGen();
+
+        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
+        if (timestamp < lastTimestamp) {
+            throw new RuntimeException(
+                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
+        }
+
+        //如果是同一时间生成的,则进行毫秒内序列
+        if (lastTimestamp == timestamp) {
+            sequence = (sequence + 1) & sequenceMask;
+            //毫秒内序列溢出
+            if (sequence == 0) {
+                //阻塞到下一个毫秒,获得新的时间戳
+                timestamp = tilNextMillis(lastTimestamp);
+            }
+        }
+        //时间戳改变,毫秒内序列重置
+        else {
+            sequence = 0L;
+        }
+
+        //上次生成ID的时间截
+        lastTimestamp = timestamp;
+
+        //移位并通过或运算拼到一起组成64位的ID
+        return ((timestamp - twepoch) << timestampLeftShift) //
+                | (datacenterId << datacenterIdShift) //
+                | (workerId << workerIdShift) //
+                | sequence;
+    }
+
+    /**
+     * 阻塞到下一个毫秒,直到获得新的时间戳
+     * @param lastTimestamp 上次生成ID的时间截
+     * @return 当前时间戳
+     */
+    protected long tilNextMillis(long lastTimestamp) {
+        long timestamp = timeGen();
+        while (timestamp <= lastTimestamp) {
+            timestamp = timeGen();
+        }
+        return timestamp;
+    }
+
+    /**
+     * 返回以毫秒为单位的当前时间
+     * @return 当前时间(毫秒)
+     */
+    protected long timeGen() {
+        return System.currentTimeMillis();
+    }
+
+
+
+
+
+
+}

+ 160 - 0
src/main/java/com/zhongzheng/common/util/SpringUtils.java

@@ -0,0 +1,160 @@
+package com.zhongzheng.common.util;
+
+import cn.hutool.core.lang.Validator;
+import org.springframework.aop.framework.AopContext;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * spring工具类 方便在非spring管理环境中获取bean
+ *
+ * @author zhongzheng
+ */
+@Component
+public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
+{
+    /** Spring应用上下文环境 */
+    private static ConfigurableListableBeanFactory beanFactory;
+
+    private static ApplicationContext applicationContext;
+
+    @Value("${poliv.sdk.userid}")
+    private String poliv_sdk_userid;
+
+    @Value("${poliv.sdk.appId}")
+    private String poliv_sdk_appId;
+
+    @Value("${poliv.sdk.appSecret}")
+    private String poliv_sdk_appSecret;
+
+    @Override
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
+    {
+        SpringUtils.beanFactory = beanFactory;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
+    {
+        SpringUtils.applicationContext = applicationContext;
+        String userId = "egsxlptzdq";
+        String appId = "ezl5uy4zei";
+        String appSecret = "2bf5bb3c31d34531943df10284edd50b";
+//        LiveGlobalConfig.init(appId,userId,appSecret);
+    }
+
+    /**
+     * 获取对象
+     *
+     * @param name
+     * @return Object 一个以所给名字注册的bean的实例
+     * @throws BeansException
+     *
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getBean(String name) throws BeansException
+    {
+        return (T) beanFactory.getBean(name);
+    }
+
+    /**
+     * 获取类型为requiredType的对象
+     *
+     * @param clz
+     * @return
+     * @throws BeansException
+     *
+     */
+    public static <T> T getBean(Class<T> clz) throws BeansException
+    {
+        T result = (T) beanFactory.getBean(clz);
+        return result;
+    }
+
+    /**
+     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
+     *
+     * @param name
+     * @return boolean
+     */
+    public static boolean containsBean(String name)
+    {
+        return beanFactory.containsBean(name);
+    }
+
+    /**
+     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
+     *
+     * @param name
+     * @return boolean
+     * @throws NoSuchBeanDefinitionException
+     *
+     */
+    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
+    {
+        return beanFactory.isSingleton(name);
+    }
+
+    /**
+     * @param name
+     * @return Class 注册对象的类型
+     * @throws NoSuchBeanDefinitionException
+     *
+     */
+    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
+    {
+        return beanFactory.getType(name);
+    }
+
+    /**
+     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
+     *
+     * @param name
+     * @return
+     * @throws NoSuchBeanDefinitionException
+     *
+     */
+    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
+    {
+        return beanFactory.getAliases(name);
+    }
+
+    /**
+     * 获取aop代理对象
+     *
+     * @param invoker
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getAopProxy(T invoker)
+    {
+        return (T) AopContext.currentProxy();
+    }
+
+    /**
+     * 获取当前的环境配置,无配置返回null
+     *
+     * @return 当前的环境配置
+     */
+    public static String[] getActiveProfiles()
+    {
+        return applicationContext.getEnvironment().getActiveProfiles();
+    }
+
+    /**
+     * 获取当前的环境配置,当有多个环境配置时,只获取第一个
+     *
+     * @return 当前的环境配置
+     */
+    public static String getActiveProfile()
+    {
+        final String[] activeProfiles = getActiveProfiles();
+        return Validator.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
+    }
+}

+ 37 - 0
src/main/java/com/zhongzheng/common/util/SqlUtil.java

@@ -0,0 +1,37 @@
+package com.zhongzheng.common.util;
+
+import cn.hutool.core.lang.Validator;
+import com.zhongzheng.common.filter.BaseException;
+
+/**
+ * sql操作工具类
+ * 
+ * @author zhongzheng
+ */
+public class SqlUtil
+{
+    /**
+     * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
+     */
+    public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
+
+    /**
+     * 检查字符,防止注入绕过
+     */
+    public static String escapeOrderBySql(String value)
+    {
+        if (Validator.isNotEmpty(value) && !isValidOrderBySql(value))
+        {
+            throw new BaseException("参数不符合规范,不能进行查询");
+        }
+        return value;
+    }
+
+    /**
+     * 验证 order by 语法是否符合规范
+     */
+    public static boolean isValidOrderBySql(String value)
+    {
+        return value.matches(SQL_PATTERN);
+    }
+}

+ 73 - 0
src/main/java/com/zhongzheng/common/util/TelPhoneUtils.java

@@ -0,0 +1,73 @@
+package com.zhongzheng.common.util;
+
+import java.util.Random;
+
+/**
+ * 手机号码工具栏
+ *
+ * @author hjl
+ */
+public class TelPhoneUtils
+{
+    //中国移动
+    public static final String[] CHINA_MOBILE = {
+            "134", "135", "136", "137", "138", "139", "150", "151", "152", "157", "158", "159",
+            "182", "183", "184", "187", "188", "178", "147", "172", "198"
+    };
+    //中国联通
+    public static final String[] CHINA_UNICOM = {
+            "130", "131", "132", "145", "155", "156", "166", "171", "175", "176", "185", "186", "166"
+    };
+    //中国电信
+    public static final String[] CHINA_TELECOME = {
+            "133", "149", "153", "173", "177", "180", "181", "189", "199"
+    };
+
+    /**
+     * 生成手机号
+     */
+    public static String createMobile() {
+        StringBuilder sb = new StringBuilder();
+        Random random = new Random();
+        int op = random.nextInt(3);//随机运营商标志位
+        String mobileThree;//手机号前三位
+        int temp;
+        switch (op) {
+            case 0:
+                mobileThree = CHINA_MOBILE[random.nextInt(CHINA_MOBILE.length)];
+                break;
+            case 1:
+                mobileThree = CHINA_UNICOM[random.nextInt(CHINA_UNICOM.length)];
+                break;
+            case 2:
+                mobileThree = CHINA_TELECOME[random.nextInt(CHINA_TELECOME.length)];
+                break;
+            default:
+                mobileThree = "op标志位有误!";
+                break;
+        }
+        if (mobileThree.length() > 3) {
+            return mobileThree;
+        }
+        sb.append(mobileThree);
+        //生成手机号后8位
+        for (int i = 0; i < 8; i++) {
+            temp = random.nextInt(10);
+            sb.append(temp);
+        }
+        return sb.toString();
+    }
+
+
+
+    /**
+     * 隐藏手机号码
+     * @param tel
+     * @return
+     */
+    public static String hideTelPhone(String tel)
+    {
+        String phoneNumber = tel.replaceAll("(\\d{3})\\d{4}(\\d{4})","$1****$2");
+        return phoneNumber;
+    }
+}

+ 96 - 0
src/main/java/com/zhongzheng/common/util/Threads.java

@@ -0,0 +1,96 @@
+package com.zhongzheng.common.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.*;
+
+/**
+ * 线程相关工具类.
+ * 
+ * @author zhongzheng
+ */
+public class Threads
+{
+    private static final Logger logger = LoggerFactory.getLogger(Threads.class);
+
+    /**
+     * sleep等待,单位为毫秒
+     */
+    public static void sleep(long milliseconds)
+    {
+        try
+        {
+            Thread.sleep(milliseconds);
+        }
+        catch (InterruptedException e)
+        {
+            return;
+        }
+    }
+
+    /**
+     * 停止线程池
+     * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
+     * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
+     * 如果仍人超時,則強制退出.
+     * 另对在shutdown时线程本身被调用中断做了处理.
+     */
+    public static void shutdownAndAwaitTermination(ExecutorService pool)
+    {
+        if (pool != null && !pool.isShutdown())
+        {
+            pool.shutdown();
+            try
+            {
+                if (!pool.awaitTermination(120, TimeUnit.SECONDS))
+                {
+                    pool.shutdownNow();
+                    if (!pool.awaitTermination(120, TimeUnit.SECONDS))
+                    {
+                        logger.info("Pool did not terminate");
+                    }
+                }
+            }
+            catch (InterruptedException ie)
+            {
+                pool.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * 打印线程异常信息
+     */
+    public static void printException(Runnable r, Throwable t)
+    {
+        if (t == null && r instanceof Future<?>)
+        {
+            try
+            {
+                Future<?> future = (Future<?>) r;
+                if (future.isDone())
+                {
+                    future.get();
+                }
+            }
+            catch (CancellationException ce)
+            {
+                t = ce;
+            }
+            catch (ExecutionException ee)
+            {
+                t = ee.getCause();
+            }
+            catch (InterruptedException ie)
+            {
+                Thread.currentThread().interrupt();
+            }
+        }
+        if (t != null)
+        {
+            logger.error(t.getMessage(), t);
+        }
+    }
+}

+ 653 - 0
src/main/java/com/zhongzheng/common/util/ToolsUtils.java

@@ -0,0 +1,653 @@
+package com.zhongzheng.common.util;
+
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.StrUtil;
+import com.google.zxing.common.BitMatrix;
+import com.zhongzheng.common.filter.CustomException;
+import io.micrometer.core.lang.NonNull;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.apache.xmlbeans.impl.util.Base64.encode;
+
+
+public class ToolsUtils {
+
+    public static final int EMU_PER_PX = 9525;
+
+    /**
+     * 获取模块编码参数
+     */
+    public static String getSmsCode()
+    {
+        Random random = new Random();
+        String result="";
+        for (int i=0;i<6;i++)
+        {
+            result+=random.nextInt(10);
+        }
+       return result;
+    }
+
+    public static String join(@NonNull CharSequence delimiter, @NonNull Iterable tokens) {
+        final Iterator<?> it = tokens.iterator();
+        if (!it.hasNext()) {
+            return "";
+        }
+        final StringBuilder sb = new StringBuilder();
+        sb.append(it.next());
+        while (it.hasNext()) {
+            sb.append(delimiter);
+            sb.append(it.next());
+        }
+        return sb.toString();
+    }
+
+    public static final int emuToPx(double emu) {
+        return DoubleUtils.div(emu, EMU_PER_PX).intValue();
+    }
+
+    public static String getEncoding(String str) {
+        String encode = "GB2312";
+        try {
+            if (str.equals(new String(str.getBytes(encode), encode))) {
+                String s = encode;
+                return s;
+            }
+        } catch (Exception exception) {
+        }
+        encode = "ISO-8859-1";
+        try {
+            if (str.equals(new String(str.getBytes(encode), encode))) {
+                String s1 = encode;
+                return s1;
+            }
+        } catch (Exception exception1) {
+        }
+        encode = "UTF-8";
+        try {
+            if (str.equals(new String(str.getBytes(encode), encode))) {
+                String s2 = encode;
+                return s2;
+            }
+        } catch (Exception exception2) {
+        }
+        encode = "GBK";
+        try {
+            if (str.equals(new String(str.getBytes(encode), encode))) {
+                String s3 = encode;
+                return s3;
+            }
+        } catch (Exception exception3) {
+        }
+        return "";
+    }
+
+    /**
+     * 字符串转换UTF-8编码
+     *
+     * @param string 字符串
+     * @return java.lang.String
+     * @date 2022/4/14.
+     */
+    public static String toUtf8String(String string) {
+        StringBuilder stringBuffer = new StringBuilder();
+        for (int i = 0; i < string.length(); i++) {
+            char c = string.charAt(i);
+            if (c <= 255) {
+                stringBuffer.append(c);
+            } else {
+                byte[] b;
+                try {
+                    b = Character.toString(c).getBytes(StandardCharsets.UTF_8);
+                } catch (Exception ex) {
+                    b = new byte[0];
+                }
+                for (int value : b) {
+                    int k = value;
+                    if (k < 0) k += 256;
+                    stringBuffer.append("%").append(Integer.toHexString(k).toUpperCase());
+                }
+            }
+        }
+        return stringBuffer.toString();
+    }
+
+    public static String StringToMd5(String psw) {
+        {
+            try {
+                MessageDigest md5 = MessageDigest.getInstance("MD5");
+                md5.update(psw.getBytes("UTF-8"));
+                byte[] encryption = md5.digest();
+
+                StringBuffer strBuf = new StringBuffer();
+                for (int i = 0; i < encryption.length; i++) {
+                    if (Integer.toHexString(0xff & encryption[i]).length() == 1) {
+                        strBuf.append("0").append(Integer.toHexString(0xff & encryption[i]));
+                    } else {
+                        strBuf.append(Integer.toHexString(0xff & encryption[i]));
+                    }
+                }
+
+                return strBuf.toString();
+            } catch (NoSuchAlgorithmException e) {
+                return "";
+            } catch (UnsupportedEncodingException e) {
+                return "";
+            }
+        }
+    }
+
+    public static String getCharAndNumr(int length) {
+        Random random = new Random();
+        StringBuffer valSb = new StringBuffer();
+        String charStr = "0123456789abcdefghijklmnopqrstuvwxyz";
+        int charLength = charStr.length();
+        for (int i = 0; i < length; i++) {
+            int index = random.nextInt(charLength);
+            valSb.append(charStr.charAt(index));
+        }
+        return valSb.toString();
+    }
+
+    public static <T> List<List<T>> splitListBycapacity(List<T> source, int capacity){
+        List<List<T>> result=new ArrayList<List<T>>();
+        if (source != null){
+            int size = source.size();
+            if (size > 0 ){
+                for (int i = 0; i < size;) {
+                    List<T> value = null;
+                    int end = i+capacity;
+                    if (end > size){
+                        end = size;
+                    }
+                    value = source.subList(i,end);
+                    i = end;
+
+                    result.add(value);
+                }
+
+            }else {
+                result = null;
+            }
+        }else {
+            result = null;
+        }
+        return result;
+    }
+
+    /**
+     * 校验签名
+     *
+     * @param token token
+     * @param signature 签名
+     * @param timestamp 时间戳
+     * @param nonce     随机数
+     * @return 布尔值
+     */
+    public static boolean checkGzhServerSignature(String token,String signature, String timestamp, String nonce) {
+        String checktext = null;
+        if (null != signature) {
+            //对ToKen,timestamp,nonce 按字典排序
+            String[] paramArr = new String[]{token, timestamp, nonce};
+            Arrays.sort(paramArr);
+            //将排序后的结果拼成一个字符串
+            String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);
+
+            try {
+                MessageDigest md = MessageDigest.getInstance("SHA-1");
+                //对接后的字符串进行sha1加密
+                byte[] digest = md.digest(content.toString().getBytes());
+                checktext = byteToStr(digest);
+            } catch (NoSuchAlgorithmException e) {
+                e.printStackTrace();
+            }
+        }
+        //将加密后的字符串与signature进行对比
+        return checktext != null ? checktext.equals(signature.toUpperCase()) : false;
+    }
+
+
+    public static String removeAllTrim(String content) {
+        if(Validator.isNotEmpty(content)){
+            Pattern p = Pattern.compile("\\s*|\t|\r|\n");
+            Matcher m = p.matcher(content);
+            content = m.replaceAll("");
+            content = content.replaceAll(" ", "");
+            content = content.replaceAll("[\\u3000-\\u303F\\s\\u00A0]","");
+        }
+        return content;
+    }
+
+    /**
+     * 将字节数组转化我16进制字符串
+     *
+     * @param byteArrays 字符数组
+     * @return 字符串
+     */
+    private static String byteToStr(byte[] byteArrays) {
+        String str = "";
+        for (int i = 0; i < byteArrays.length; i++) {
+            str += byteToHexStr(byteArrays[i]);
+        }
+        return str;
+    }
+
+    /**
+     * 将字节转化为十六进制字符串
+     *
+     * @param myByte 字节
+     * @return 字符串
+     */
+    private static String byteToHexStr(byte myByte) {
+        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+        char[] tampArr = new char[2];
+        tampArr[0] = Digit[(myByte >>> 4) & 0X0F];
+        tampArr[1] = Digit[myByte & 0X0F];
+        String str = new String(tampArr);
+        return str;
+    }
+
+    /**
+     * 将文件转换成Byte数组
+     */
+    public static byte[] getBytesByFile(String pathStr) {
+        File file = new File(pathStr);
+        try {
+            FileInputStream fis = new FileInputStream(file);
+            ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
+            byte[] b = new byte[1000];
+            int n;
+            while ((n = fis.read(b)) != -1) {
+                bos.write(b, 0, n);
+            }
+            fis.close();
+            byte[] data = bos.toByteArray();
+            bos.close();
+            return data;
+        } catch (IOException e) {
+        }
+        return null;
+    }
+
+    public static String encodetoStr(byte[] src) {
+        byte[] encoded = encode(src);
+        return new String(encoded,0,0,encoded.length);
+    }
+
+    /**
+     * 不够位数的在前面补0,保留num的长度位数字
+     * @param code
+     * @return
+     */
+    public static String autoGenericCode(String code, int num) {
+        String result = "";
+        // 保留num的位数
+        // 0 代表前面补充0
+        // num 代表长度为4
+        // d 代表参数为正数型
+        result = String.format("%0" + num + "d", Integer.parseInt(code));
+
+        return result;
+    }
+
+    public static Boolean checkSignFromOldSys(String stamp,String sign) {
+        String newSign = stamp+"pubilc2022";
+        if(!sign.equals(ToolsUtils.EncoderByMd5(newSign))){
+            return false;
+        }
+        if((Long.parseLong(stamp)+10L>(DateUtils.getNowTime().longValue()))&&(Long.parseLong(stamp)<(DateUtils.getNowTime().longValue()-10L))){
+            return false;
+        }
+        return true;
+    }
+
+    public static Boolean checkOrderSignFromOldSys(String orderSn,String stamp,String sign) {
+        String newSign = orderSn+stamp+"pubilc2022";
+        if(!sign.equals(ToolsUtils.EncoderByMd5(newSign))){
+            return false;
+        }
+        if((Long.parseLong(stamp)+10L>(DateUtils.getNowTime().longValue()))&&(Long.parseLong(stamp)<(DateUtils.getNowTime().longValue()-10L))){
+            return false;
+        }
+        return true;
+    }
+
+    public static Boolean checkSignCwSnFromOldSys(String cwSn,String stamp,String sign) {
+        String newSign = cwSn+stamp+"pubilc2022";
+        if(!sign.equals(ToolsUtils.EncoderByMd5(newSign))){
+            return false;
+        }
+        if((Long.parseLong(stamp)+10L>(DateUtils.getNowTime().longValue()))&&(Long.parseLong(stamp)<(DateUtils.getNowTime().longValue()-10L))){
+            return false;
+        }
+        return true;
+    }
+
+    public static String EncoderByMd5WithUtf(String str) {
+        String result = "";
+        MessageDigest md5 = null;
+        try {
+            md5 = MessageDigest.getInstance("MD5");
+            // 这句是关键
+            md5.update(str.getBytes("UTF-8"));
+        } catch (NoSuchAlgorithmException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (UnsupportedEncodingException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        byte b[] = md5.digest();
+        int i;
+        StringBuffer buf = new StringBuffer("");
+        for (int offset = 0; offset < b.length; offset++) {
+            i = b[offset];
+            if (i < 0)
+                i += 256;
+            if (i < 16)
+                buf.append("0");
+            buf.append(Integer.toHexString(i));
+        }
+        result = buf.toString();
+
+        return result;
+    }
+
+
+
+    public static String EncoderByMd5(String str) {
+        String result = "";
+        MessageDigest md5 = null;
+        try {
+            md5 = MessageDigest.getInstance("MD5");
+            // 这句是关键
+            md5.update(str.getBytes("gbk"));
+        } catch (NoSuchAlgorithmException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (UnsupportedEncodingException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        byte b[] = md5.digest();
+        int i;
+        StringBuffer buf = new StringBuffer("");
+        for (int offset = 0; offset < b.length; offset++) {
+            i = b[offset];
+            if (i < 0)
+                i += 256;
+            if (i < 16)
+                buf.append("0");
+            buf.append(Integer.toHexString(i));
+        }
+        result = buf.toString();
+
+        return result;
+    }
+
+    public static boolean verifEasyPwd(String passWord) {
+        if(Validator.isEmpty(passWord)){
+            return false;
+        }
+        if(passWord.length()<6||passWord.length()>16){
+            throw new CustomException("密码长度限制6到16位");
+        }
+        return true;
+    }
+
+    public static boolean verifPwd(String passWord) {
+        if(Validator.isEmpty(passWord)){
+            return false;
+        }
+        /*if(passWord.length()<8||passWord.length()>18){
+            return false;
+        }*/
+        String regExp = "^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\\d)(?=.*?[!#@*&.])[a-zA-Z\\d!#@*&.]{8,16}$";
+        Pattern p = Pattern.compile(regExp);
+        Matcher m = p.matcher(passWord);
+        if (m.matches()){
+            return true;
+        } else {
+            throw new CustomException("密码应由8-16位数字、大小写字母、符号组成");
+        }
+    }
+
+    public static String getTenantId() {
+        String TenantId =  ServletUtils.getResponse().getHeader("companyid");
+        if(!StrUtil.isNotBlank(TenantId)||TenantId==null){
+            TenantId = ServletUtils.getRequest().getHeader("companyid");
+        }
+        if(Validator.isNotEmpty(TenantId)){
+            if(TenantId.equals("undefined")){
+                throw new CustomException("企业ID错误");
+            }
+        }
+        return TenantId;
+    }
+
+    private static int getRandom(int count) {
+        return (int) Math.round(Math.random() * (count));
+    }
+
+    public static String getRandomString(int length){
+        String string = "abcdefghijklmnopqrstuvwxyz";
+        StringBuffer sb = new StringBuffer();
+        int len = string.length();
+        for (int i = 0; i < length; i++) {
+            sb.append(string.charAt(getRandom(len-1)));
+        }
+        return sb.toString();
+    }
+
+    public static BufferedImage toBufferedImage(BitMatrix matrix) {
+        int black = 0xFF000000;
+        int white  = 0x00FFFFFF;
+        int width = matrix.getWidth();
+        int height = matrix.getHeight();
+        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        for (int x = 0; x < width; x++) {
+            for (int y = 0; y < height; y++) {
+                image.setRGB(x, y, matrix.get(x, y) ? black : white);
+            }
+        }
+        int imgHeight = image.getHeight();//取得图片的长和宽
+        int imgWidth = image.getWidth();
+        int c = image.getRGB(3, 3);
+        int alpha = 10;
+        //防止越位
+        if (alpha < 0) {
+            alpha = 0;
+        } else if (alpha > 10) {
+            alpha = 10;
+        }
+        BufferedImage tmpImg = new BufferedImage(imgWidth, imgHeight,BufferedImage.TYPE_4BYTE_ABGR);//新建一个类型支持透明的BufferedImage
+        for(int i = 0; i < imgWidth; ++i)//把原图片的内容复制到新的图片,同时把背景设为透明
+        {
+            for(int j = 0; j < imgHeight; ++j){
+                //把背景设为透明
+                if(image.getRGB(i, j) == c){
+                    tmpImg .setRGB(i, j, c & 0x00ffffff);
+                }
+                //设置透明度
+                else{
+                    int rgb = tmpImg .getRGB(i, j);
+                    rgb = ((alpha * 255 / 10) << 24) | (rgb & 0x00ffffff);
+                    tmpImg .setRGB(i, j, rgb);
+                }
+            }
+        }
+        return tmpImg ;
+    }
+
+    public static String solve(String num) {
+        if (num == null) {
+            return null;
+        }
+        // 判断是否有小数
+        int index = num.indexOf(".");
+        if (index >= 0) {
+            String integer = num.substring(0, index);
+            String decimal = num.substring(index);
+            // 分隔后的整数+小数拼接起来
+            return addSeparator(integer) + decimal;
+        } else {
+            return addSeparator(num);
+        }
+    }
+
+    // 添加分隔符
+    public static String addSeparator(String num) {
+        int length = num.length();
+        ArrayList list = new ArrayList();
+        while (length > 3) {
+            list.add(num.substring(length - 3, length));
+            length = length - 3;
+        }
+        // 将前面小于三位的数字添加到ArrayList中
+        list.add(num.substring(0, length));
+        StringBuffer buffer = new StringBuffer();
+        // 倒序拼接
+        for (int i = list.size() - 1; i > 0; i--) {
+            buffer.append(list.get(i) + ",");
+        }
+        buffer.append(list.get(0));
+        return buffer.toString();
+    }
+
+    public static String dataSign(String sourceMsg, String pKey) throws NoSuchAlgorithmException {
+        String tempPKey = MD5PubHasher(pKey.getBytes(StandardCharsets.UTF_8));
+        tempPKey = sourceMsg + tempPKey;
+        tempPKey = MD5PubHasher(tempPKey.getBytes(StandardCharsets.UTF_8));
+        return tempPKey;
+    }
+
+    public static String MD5PubHasher(byte[] hashText)  {
+        try {
+            MessageDigest md5 = MessageDigest.getInstance("MD5");
+            byte[] b = md5.digest(hashText);
+            StringBuilder ret = new StringBuilder();
+            for (int i = 0; i < b.length; i++) {
+                ret.append(String.format("%02x", b[i]));
+            }
+            return ret.toString();
+        }catch (NoSuchAlgorithmException e){
+            return null;
+        }
+    }
+
+    public static String encryptDes(String source, byte[] key, byte[] iv) throws Exception {
+        SecretKey secretKey = new SecretKeySpec(key, "DES");
+        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
+        byte[] inputByteArray = source.getBytes(StandardCharsets.UTF_8);
+        byte[] encryptedByteArray = cipher.doFinal(inputByteArray);
+        return new String(Base64.getEncoder().encode(encryptedByteArray));
+    }
+
+    public static String decryptDes(String source, byte[] key, byte[] iv) throws Exception {
+        SecretKey secretKey = new SecretKeySpec(key, "DES");
+        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
+        byte[] inputByteArray = Base64.getDecoder().decode(source.getBytes(StandardCharsets.UTF_8));
+        byte[] decryptedByteArray = cipher.doFinal(inputByteArray);
+        return new String(decryptedByteArray, StandardCharsets.UTF_8);
+    }
+
+    /*public static String encryptDesNew(String source, String pass) throws Exception {
+        byte[] rgbKey = pass.getBytes("UTF-8");
+        byte[] rgbIV = pass.getBytes("UTF-8");
+                DESedeKeySpec desKeySpec = new DESedeKeySpec(rgbKey);
+             SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+             SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+             Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
+            byte[] inputByteArray = source.getBytes("UTF-8");
+            IvParameterSpec ivParameterSpec = new IvParameterSpec(rgbIV);
+            cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
+            byte[] encrypted = cipher.doFinal(inputByteArray);
+            return Base64.getEncoder().encodeToString(encrypted);}
+    }*/
+
+    /**
+
+     Des解密
+     @param source 源字符串
+     @param pass 密钥,长度必须8位
+     @return 解密后的字符串 */
+    /*public static String decryptDesNew(String source, String pass) throws Exception {
+        byte[] rgbKey = pass.getBytes("UTF-8");
+        byte[] rgbIV = pass.getBytes("UTF-8");
+        DESedeKeySpec desKeySpec = new DESedeKeySpec(rgbKey);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding")) {
+        byte[] inputByteArray = Base64.getDecoder().decode(source);
+        IvParameterSpec ivParameterSpec = new IvParameterSpec(rgbIV);
+        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
+        byte[] decrypted = cipher.doFinal(inputByteArray);
+        return new String(decrypted, "UTF-8");
+        }
+    }*/
+
+
+ public static String encryptDesNew(String source, String pass) throws Exception {
+        byte[] rgbKey = pass.getBytes(StandardCharsets.UTF_8);
+        byte[] rgbIV = pass.getBytes(StandardCharsets.UTF_8);
+        DESKeySpec desKeySpec = new DESKeySpec(rgbKey);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        byte[] inputByteArray = source.getBytes(StandardCharsets.UTF_8);
+        IvParameterSpec ivParameterSpec = new IvParameterSpec(rgbIV);
+        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
+        byte[] encryptedByteArray = cipher.doFinal(inputByteArray);
+        return Base64.getEncoder().encodeToString(encryptedByteArray);
+    }
+
+   public static String decryptDesNew(String source, String pass) throws Exception {
+        byte[] rgbKey = pass.getBytes(StandardCharsets.UTF_8);
+        byte[] rgbIV = pass.getBytes(StandardCharsets.UTF_8);
+        DESKeySpec desKeySpec = new DESKeySpec(rgbKey);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        byte[] inputByteArray = Base64.getDecoder().decode(source);
+        IvParameterSpec ivParameterSpec = new IvParameterSpec(rgbIV);
+        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
+        byte[] decryptedByteArray = cipher.doFinal(inputByteArray);
+        return new String(decryptedByteArray, StandardCharsets.UTF_8);
+    }
+
+    private static String base64Encode(byte[] bytes) throws IOException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        Base64.getEncoder().wrap(output).write(bytes);
+        return output.toString(StandardCharsets.UTF_8.name());
+    }
+
+    /*private static byte[] base64Decode(String str) throws IOException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        Base64.getDecoder().wrap(output).write(str.getBytes(StandardCharsets.UTF_8));
+        return output.toByteArray();
+    }*/
+    private static byte[] base64Decode(String str) throws IOException {
+        byte[] decodedString = Base64.getDecoder().decode(new String(str).getBytes("UTF-8"));
+        return decodedString;
+    }
+
+}

+ 137 - 0
src/main/java/com/zhongzheng/controller/CaptchaController.java

@@ -0,0 +1,137 @@
+package com.zhongzheng.controller;
+
+import cn.hutool.captcha.AbstractCaptcha;
+import cn.hutool.captcha.CircleCaptcha;
+import cn.hutool.captcha.LineCaptcha;
+import cn.hutool.captcha.ShearCaptcha;
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.captcha.generator.MathGenerator;
+import cn.hutool.captcha.generator.RandomGenerator;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import com.github.xiaoymin.knife4j.annotations.ApiSupport;
+import com.github.xiaoymin.knife4j.annotations.DynamicParameter;
+import com.github.xiaoymin.knife4j.annotations.DynamicResponseParameters;
+import com.zhongzheng.common.croe.RedisCache;
+import com.zhongzheng.common.filter.Constants;
+import com.zhongzheng.common.model.AjaxResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 验证码操作处理
+ *
+ * @author hjl
+ */
+@Api(tags ="系统操作管理")
+@ApiSupport(order = 1)
+@RestController
+public class CaptchaController {
+
+	// 圆圈干扰验证码
+	@Resource(name = "CircleCaptcha")
+	private CircleCaptcha circleCaptcha;
+	// 线段干扰的验证码
+	@Resource(name = "LineCaptcha")
+	private LineCaptcha lineCaptcha;
+	// 扭曲干扰验证码
+	@Resource(name = "ShearCaptcha")
+	private ShearCaptcha shearCaptcha;
+
+	@Autowired
+	private RedisCache redisCache;
+
+	// 验证码类型
+	@Value("${captcha.captchaType}")
+	private String captchaType;
+	// 验证码类别
+	@Value("${captcha.captchaCategory}")
+	private String captchaCategory;
+	// 数字验证码位数
+	@Value("${captcha.captchaNumberLength}")
+	private int numberLength;
+	// 字符验证码长度
+	@Value("${captcha.captchaCharLength}")
+	private int charLength;
+
+	/**
+	 * 生成验证码
+	 */
+	@ApiOperation("获取验证码")
+	@DynamicResponseParameters(name = "CodeMapModel",properties = {
+			@DynamicParameter(name = "uuid",value = "验证码uuid"),
+			@DynamicParameter(name = "img",value = "验证码图片base64"),
+	})
+	@GetMapping("/captchaImage")
+	public AjaxResult getCode() {
+		// 保存验证码信息
+		String uuid = IdUtil.simpleUUID();
+		String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
+		String code = null;
+		// 生成验证码
+		CodeGenerator codeGenerator;
+		AbstractCaptcha captcha;
+		switch (captchaType) {
+			case "math":
+				codeGenerator = new MathGenerator(numberLength);
+				break;
+			case "char":
+				codeGenerator = new RandomGenerator(charLength);
+				break;
+			default:
+				throw new IllegalArgumentException("验证码类型异常");
+		}
+		switch (captchaCategory) {
+			case "line":
+				captcha = lineCaptcha;
+				break;
+			case "circle":
+				captcha = circleCaptcha;
+				break;
+			case "shear":
+				captcha = shearCaptcha;
+				break;
+			default:
+				throw new IllegalArgumentException("验证码类别异常");
+		}
+		captcha.setGenerator(codeGenerator);
+		captcha.createCode();
+		if ("math".equals(captchaType)) {
+			code = getCodeResult(captcha.getCode());
+		} else if ("char".equals(captchaType)) {
+			code = captcha.getCode();
+		}
+		redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
+		Map<String,Object> map = new HashMap();
+		map.put("uuid", uuid);
+		map.put("img", captcha.getImageBase64());
+		return AjaxResult.success(map);
+	}
+
+	private String getCodeResult(String capStr) {
+		int a = Convert.toInt(StrUtil.sub(capStr, 0, numberLength).trim());
+		char operator = capStr.charAt(numberLength);
+		int b = Convert.toInt(StrUtil.sub(capStr, numberLength + 1, numberLength + 1 + numberLength).trim());
+		switch (operator) {
+			case '*':
+				return a * b + "";
+			case '+':
+				return a + b + "";
+			case '-':
+				return a - b + "";
+			default:
+				return "";
+		}
+	}
+
+}

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.