Bläddra i källkod

初始化仓库

瞳风 1 år sedan
incheckning
3e7cca103e
100 ändrade filer med 9248 tillägg och 0 borttagningar
  1. 37 0
      .gitignore
  2. 191 0
      LICENSE
  3. 112 0
      README.md
  4. 32 0
      eladmin-common/pom.xml
  5. 30 0
      eladmin-common/src/main/java/me/zhengjie/annotation/AnonymousAccess.java
  6. 47 0
      eladmin-common/src/main/java/me/zhengjie/annotation/DataPermission.java
  7. 49 0
      eladmin-common/src/main/java/me/zhengjie/annotation/Limit.java
  8. 90 0
      eladmin-common/src/main/java/me/zhengjie/annotation/Query.java
  9. 91 0
      eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousDeleteMapping.java
  10. 90 0
      eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousGetMapping.java
  11. 91 0
      eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPatchMapping.java
  12. 91 0
      eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPostMapping.java
  13. 91 0
      eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPutMapping.java
  14. 99 0
      eladmin-common/src/main/java/me/zhengjie/aspect/LimitAspect.java
  15. 27 0
      eladmin-common/src/main/java/me/zhengjie/aspect/LimitType.java
  16. 41 0
      eladmin-common/src/main/java/me/zhengjie/base/BaseDTO.java
  17. 86 0
      eladmin-common/src/main/java/me/zhengjie/base/BaseEntity.java
  18. 53 0
      eladmin-common/src/main/java/me/zhengjie/base/BaseMapper.java
  19. 165 0
      eladmin-common/src/main/java/me/zhengjie/base/ResponseCodeConst.java
  20. 139 0
      eladmin-common/src/main/java/me/zhengjie/base/ResponseDTO.java
  21. 186 0
      eladmin-common/src/main/java/me/zhengjie/base/ResultCode.java
  22. 45 0
      eladmin-common/src/main/java/me/zhengjie/config/AuditorConfig.java
  23. 38 0
      eladmin-common/src/main/java/me/zhengjie/config/ElAdminProperties.java
  24. 37 0
      eladmin-common/src/main/java/me/zhengjie/config/ElPermissionConfig.java
  25. 60 0
      eladmin-common/src/main/java/me/zhengjie/config/FileProperties.java
  26. 217 0
      eladmin-common/src/main/java/me/zhengjie/config/RedisConfig.java
  27. 38 0
      eladmin-common/src/main/java/me/zhengjie/config/RsaProperties.java
  28. 148 0
      eladmin-common/src/main/java/me/zhengjie/config/SwaggerConfig.java
  29. 14 0
      eladmin-common/src/main/java/me/zhengjie/exception/AccountNoActivationException.java
  30. 98 0
      eladmin-common/src/main/java/me/zhengjie/exception/BadConfigurationException.java
  31. 40 0
      eladmin-common/src/main/java/me/zhengjie/exception/BadRequestException.java
  32. 43 0
      eladmin-common/src/main/java/me/zhengjie/exception/EntityExistException.java
  33. 34 0
      eladmin-common/src/main/java/me/zhengjie/exception/EntityNotFoundException.java
  34. 52 0
      eladmin-common/src/main/java/me/zhengjie/exception/handler/ApiError.java
  35. 209 0
      eladmin-common/src/main/java/me/zhengjie/exception/handler/GlobalExceptionHandler.java
  36. 58 0
      eladmin-common/src/main/java/me/zhengjie/utils/CacheKey.java
  37. 43 0
      eladmin-common/src/main/java/me/zhengjie/utils/CallBack.java
  38. 65 0
      eladmin-common/src/main/java/me/zhengjie/utils/ClientCache.java
  39. 47 0
      eladmin-common/src/main/java/me/zhengjie/utils/CloseUtil.java
  40. 301 0
      eladmin-common/src/main/java/me/zhengjie/utils/DateFormatUtils.java
  41. 220 0
      eladmin-common/src/main/java/me/zhengjie/utils/DateUtil.java
  42. 47 0
      eladmin-common/src/main/java/me/zhengjie/utils/ElAdminConstant.java
  43. 100 0
      eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java
  44. 374 0
      eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java
  45. 48 0
      eladmin-common/src/main/java/me/zhengjie/utils/IpAddressUtil.java
  46. 63 0
      eladmin-common/src/main/java/me/zhengjie/utils/PageUtil.java
  47. 208 0
      eladmin-common/src/main/java/me/zhengjie/utils/QueryHelp.java
  48. 714 0
      eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java
  49. 33 0
      eladmin-common/src/main/java/me/zhengjie/utils/RequestHolder.java
  50. 198 0
      eladmin-common/src/main/java/me/zhengjie/utils/RsaUtils.java
  51. 73 0
      eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java
  52. 145 0
      eladmin-common/src/main/java/me/zhengjie/utils/SpringContextHolder.java
  53. 289 0
      eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java
  54. 37 0
      eladmin-common/src/main/java/me/zhengjie/utils/ThrowableUtil.java
  55. 66 0
      eladmin-common/src/main/java/me/zhengjie/utils/TranslatorUtil.java
  56. 45 0
      eladmin-common/src/main/java/me/zhengjie/utils/ValidationUtil.java
  57. 50 0
      eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeBiEnum.java
  58. 46 0
      eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeEnum.java
  59. 53 0
      eladmin-common/src/main/java/me/zhengjie/utils/enums/DataScopeEnum.java
  60. 74 0
      eladmin-common/src/main/java/me/zhengjie/utils/enums/RequestMethodEnum.java
  61. 31 0
      eladmin-common/src/main/java/me/zhengjie/websocket/MsgType.java
  62. 33 0
      eladmin-common/src/main/java/me/zhengjie/websocket/SocketMsg.java
  63. 164 0
      eladmin-common/src/main/java/me/zhengjie/websocket/WebSocketServer.java
  64. 26 0
      eladmin-common/src/test/java/me/zhengjie/utils/DateUtilsTest.java
  65. 32 0
      eladmin-common/src/test/java/me/zhengjie/utils/EncryptUtilsTest.java
  66. 36 0
      eladmin-common/src/test/java/me/zhengjie/utils/FileUtilTest.java
  67. 43 0
      eladmin-common/src/test/java/me/zhengjie/utils/StringUtilsTest.java
  68. 39 0
      eladmin-generator/pom.xml
  69. 97 0
      eladmin-generator/src/main/java/me/zhengjie/domain/ColumnInfo.java
  70. 78 0
      eladmin-generator/src/main/java/me/zhengjie/domain/GenConfig.java
  71. 48 0
      eladmin-generator/src/main/java/me/zhengjie/domain/vo/TableInfo.java
  72. 34 0
      eladmin-generator/src/main/java/me/zhengjie/repository/ColumnInfoRepository.java
  73. 33 0
      eladmin-generator/src/main/java/me/zhengjie/repository/GenConfigRepository.java
  74. 51 0
      eladmin-generator/src/main/java/me/zhengjie/rest/GenConfigController.java
  75. 107 0
      eladmin-generator/src/main/java/me/zhengjie/rest/GeneratorController.java
  76. 40 0
      eladmin-generator/src/main/java/me/zhengjie/service/GenConfigService.java
  77. 96 0
      eladmin-generator/src/main/java/me/zhengjie/service/GeneratorService.java
  78. 67 0
      eladmin-generator/src/main/java/me/zhengjie/service/impl/GenConfigServiceImpl.java
  79. 205 0
      eladmin-generator/src/main/java/me/zhengjie/service/impl/GeneratorServiceImpl.java
  80. 54 0
      eladmin-generator/src/main/java/me/zhengjie/utils/ColUtil.java
  81. 421 0
      eladmin-generator/src/main/java/me/zhengjie/utils/GenUtil.java
  82. 22 0
      eladmin-logging/pom.xml
  83. 31 0
      eladmin-logging/src/main/java/me/zhengjie/annotation/Log.java
  84. 98 0
      eladmin-logging/src/main/java/me/zhengjie/aspect/LogAspect.java
  85. 154 0
      eladmin-logging/src/main/java/me/zhengjie/aspect/LogRequestParam.java
  86. 80 0
      eladmin-logging/src/main/java/me/zhengjie/domain/Log.java
  87. 39 0
      eladmin-logging/src/main/java/me/zhengjie/repository/LogRepository.java
  88. 109 0
      eladmin-logging/src/main/java/me/zhengjie/rest/LogController.java
  89. 92 0
      eladmin-logging/src/main/java/me/zhengjie/service/LogService.java
  90. 46 0
      eladmin-logging/src/main/java/me/zhengjie/service/dto/LogErrorDTO.java
  91. 39 0
      eladmin-logging/src/main/java/me/zhengjie/service/dto/LogQueryCriteria.java
  92. 40 0
      eladmin-logging/src/main/java/me/zhengjie/service/dto/LogSmallDTO.java
  93. 168 0
      eladmin-logging/src/main/java/me/zhengjie/service/impl/LogServiceImpl.java
  94. 31 0
      eladmin-logging/src/main/java/me/zhengjie/service/mapstruct/LogErrorMapper.java
  95. 31 0
      eladmin-logging/src/main/java/me/zhengjie/service/mapstruct/LogSmallMapper.java
  96. 36 0
      eladmin-mnt/pom.xml
  97. 68 0
      eladmin-mnt/src/main/java/me/zhengjie/mnt/domain/App.java
  98. 61 0
      eladmin-mnt/src/main/java/me/zhengjie/mnt/domain/Database.java
  99. 60 0
      eladmin-mnt/src/main/java/me/zhengjie/mnt/domain/Deploy.java
  100. 0 0
      eladmin-mnt/src/main/java/me/zhengjie/mnt/domain/DeployHistory.java

+ 37 - 0
.gitignore

@@ -0,0 +1,37 @@
+# Compiled class file
+*.class
+
+.DS_Store
+.idea/
+*.iml
+target/
+ 
+# Log file
+*.log
+
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+*.settings
+*.project
+.settings/
+*.settings 
+.classpath 
+.project 
+*.idea 
+logs 
+log
+.factorypath 
+*.mvn 
+.mvn 
+*.iml 
+*.factorypath 
+*.node_modules 
+node_modules 
+.sts4-cache
+/server/eladmin-system/*.factorypath
+/server/eladmin-system/*.classpath

+ 191 - 0
LICENSE

@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "{}" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright 2019-2020 Zheng Jie
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 112 - 0
README.md

@@ -0,0 +1,112 @@
+<h1 style="text-align: center">EL-ADMIN 后台管理系统</h1>
+<div style="text-align: center">
+
+[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/elunez/eladmin/blob/master/LICENSE)
+[![star](https://gitee.com/elunez/eladmin/badge/star.svg?theme=white)](https://gitee.com/elunez/eladmin)
+[![GitHub stars](https://img.shields.io/github/stars/elunez/eladmin.svg?style=social&label=Stars)](https://github.com/elunez/eladmin)
+[![GitHub forks](https://img.shields.io/github/forks/elunez/eladmin.svg?style=social&label=Fork)](https://github.com/elunez/eladmin)
+
+</div>
+
+#### 项目简介
+一个基于 Spring Boot 2.1.0 、 Spring Boot Jpa、 JWT、Spring Security、Redis、Vue的前后端分离的后台管理系统
+
+**开发文档:**  [https://el-admin.vip](https://el-admin.vip)
+
+**体验地址:**  [https://el-admin.xin](https://el-admin.xin)
+
+**账号密码:** `admin / 123456`
+
+#### 项目源码
+
+|     |   后端源码  |   前端源码  |
+|---  |--- | --- |
+|  github   |  https://github.com/elunez/eladmin   |  https://github.com/elunez/eladmin-web   |
+|  码云   |  https://gitee.com/elunez/eladmin   |  https://gitee.com/elunez/eladmin-web   |
+
+#### 主要特性
+- 使用最新技术栈,社区资源丰富。
+- 高效率开发,代码生成器可一键生成前后端代码
+- 支持数据字典,可方便地对一些状态进行管理
+- 支持接口限流,避免恶意请求导致服务层压力过大
+- 支持接口级别的功能权限与数据权限,可自定义操作
+- 自定义权限注解与匿名接口注解,可快速对接口拦截与放行
+- 对一些常用地前端组件封装:表格数据请求、数据字典等
+- 前后端统一异常拦截处理,统一输出异常,避免繁琐的判断
+- 支持在线用户管理与服务器性能监控,支持限制单用户登录
+- 支持运维管理,可方便地对远程服务器的应用进行部署与管理
+
+####  系统功能
+- 用户管理:提供用户的相关配置,新增用户后,默认密码为123456
+- 角色管理:对权限与菜单进行分配,可根据部门设置角色的数据权限
+- 菜单管理:已实现菜单动态路由,后端可配置化,支持多级菜单
+- 部门管理:可配置系统组织架构,树形表格展示
+- 岗位管理:配置各个部门的职位
+- 字典管理:可维护常用一些固定的数据,如:状态,性别等
+- 系统日志:记录用户操作日志与异常日志,方便开发人员定位排错
+- SQL监控:采用druid 监控数据库访问性能,默认用户名admin,密码123456
+- 定时任务:整合Quartz做定时任务,加入任务日志,任务运行情况一目了然
+- 代码生成:高灵活度生成前后端代码,减少大量重复的工作任务
+- 邮件工具:配合富文本,发送html格式的邮件
+- 七牛云存储:可同步七牛云存储的数据到系统,无需登录七牛云直接操作云数据
+- 支付宝支付:整合了支付宝支付并且提供了测试账号,可自行测试
+- 服务监控:监控服务器的负载情况
+- 运维管理:一键部署你的应用
+
+#### 项目结构
+项目采用按功能分模块的开发方式,结构如下
+
+- `eladmin-common` 为系统的公共模块,各种工具类,公共配置存在该模块
+
+- `eladmin-system` 为系统核心模块也是项目入口模块,也是最终需要打包部署的模块
+
+- `eladmin-logging` 为系统的日志模块,其他模块如果需要记录日志需要引入该模块
+
+- `eladmin-tools` 为第三方工具模块,包含:图床、邮件、云存储、本地存储、支付宝
+
+- `eladmin-generator` 为系统的代码生成模块,代码生成的模板在 system 模块中
+
+#### 详细结构
+
+```
+- eladmin-common 公共模块
+    - annotation 为系统自定义注解
+    - aspect 自定义注解的切面
+    - base 提供了Entity、DTO基类和mapstruct的通用mapper
+    - config 自定义权限实现、redis配置、swagger配置、Rsa配置等
+    - exception 项目统一异常的处理
+    - utils 系统通用工具
+- eladmin-system 系统核心模块(系统启动入口)
+	- config 配置跨域与静态资源,与数据权限
+	    - thread 线程池相关
+	- modules 系统相关模块(登录授权、系统监控、定时任务、运维管理等)
+- eladmin-logging 系统日志模块
+- eladmin-tools 系统第三方工具模块
+- eladmin-generator 系统代码生成模块
+```
+
+#### 特别鸣谢
+
+- 感谢 [JetBrains](https://www.jetbrains.com/) 提供的非商业开源软件开发授权
+
+- 感谢 [七牛云](https://www.qiniu.com/) 提供的免费云存储与CDN加速支持
+
+- 感谢 [PanJiaChen](https://github.com/PanJiaChen/vue-element-admin) 大佬提供的前端模板
+
+- 感谢 [Moxun](https://github.com/moxun1639) 大佬提供的前端 Curd 通用组件
+
+- 感谢 [zhy6599](https://gitee.com/zhy6599) 大佬提供的后端运维管理相关功能
+
+- 感谢 [j.yao.SUSE](https://github.com/everhopingandwaiting) 大佬提供的匿名接口与Redis限流等功能
+
+- 感谢 [d15801543974](https://github.com/d15801543974) 大佬提供的基于注解的通用查询方式
+
+ 
+https://blog.csdn.net/weixin_44120354/article/details/121116555 linux单机部署minio
+http://docs.minio.org.cn/docs/master/minio-deployment-quickstart-guide 中文文档手册
+https://blog.csdn.net/tianmunan/article/details/118874734 配置主从模式
+https://blog.csdn.net/duan196_118/article/details/105294267 mysql安装 centos系统的
+
+
+
+

+ 32 - 0
eladmin-common/pom.xml

@@ -0,0 +1,32 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>eladmin</artifactId>
+        <groupId>me.zhengjie</groupId>
+        <version>2.6</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <properties>
+        <hutool.version>5.3.4</hutool.version>
+        <netty-socketio.version>1.7.11</netty-socketio.version>
+    </properties>
+
+    <artifactId>eladmin-common</artifactId>
+    <name>公共模块</name>
+
+    <dependencies>
+        <!--工具包-->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>${hutool.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.corundumstudio.socketio</groupId>
+            <artifactId>netty-socketio</artifactId>
+            <version>${netty-socketio.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 30 - 0
eladmin-common/src/main/java/me/zhengjie/annotation/AnonymousAccess.java

@@ -0,0 +1,30 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * @author jacky
+ *  用于标记匿名访问方法
+ */
+@Inherited
+@Documented
+@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AnonymousAccess {
+
+}

+ 47 - 0
eladmin-common/src/main/java/me/zhengjie/annotation/DataPermission.java

@@ -0,0 +1,47 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ *   用于判断是否过滤数据权限
+ *   1、如果没有用到 @OneToOne 这种关联关系,只需要填写 fieldName [参考:DeptQueryCriteria.class]
+ *   2、如果用到了 @OneToOne ,fieldName 和 joinName 都需要填写,拿UserQueryCriteria.class举例:
+ *   应该是 @DataPermission(joinName = "dept", fieldName = "id")
+ * </p>
+ * @author Zheng Jie
+ * @website https://el-admin.vip
+ * @date 2020-05-07
+ **/
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DataPermission {
+
+    /**
+     * Entity 中的字段名称
+     */
+    String fieldName() default "";
+
+    /**
+     * Entity 中与部门关联的字段名称
+     */
+    String joinName() default "";
+}

+ 49 - 0
eladmin-common/src/main/java/me/zhengjie/annotation/Limit.java

@@ -0,0 +1,49 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.annotation;
+
+import me.zhengjie.aspect.LimitType;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author jacky
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Limit {
+
+    // 资源名称,用于描述接口功能
+    String name() default "";
+
+    // 资源 key
+    String key() default "";
+
+    // key prefix
+    String prefix() default "";
+
+    // 时间的,单位秒
+    int period();
+
+    // 限制访问次数
+    int count();
+
+    // 限制类型
+    LimitType limitType() default LimitType.CUSTOMER;
+
+}

+ 90 - 0
eladmin-common/src/main/java/me/zhengjie/annotation/Query.java

@@ -0,0 +1,90 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-6-4 13:52:30
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Query {
+
+    // Dong ZhaoYang 2017/8/7 基本对象的属性名
+    String propName() default "";
+    // Dong ZhaoYang 2017/8/7 查询方式
+    Type type() default Type.EQUAL;
+
+    /**
+     * 连接查询的属性名,如User类中的dept
+     */
+    String joinName() default "";
+
+    /**
+     * 默认左连接
+     */
+    Join join() default Join.LEFT;
+
+    /**
+     * 多字段模糊搜索,仅支持String类型字段,多个用逗号隔开, 如@Query(blurry = "email,username")
+     */
+    String blurry() default "";
+
+    enum Type {
+        // jie 2019/6/4 相等
+        EQUAL
+        // Dong ZhaoYang 2017/8/7 大于等于
+        , GREATER_THAN
+        // Dong ZhaoYang 2017/8/7 小于等于
+        , LESS_THAN
+        // Dong ZhaoYang 2017/8/7 中模糊查询
+        , INNER_LIKE
+        // Dong ZhaoYang 2017/8/7 左模糊查询
+        , LEFT_LIKE
+        // Dong ZhaoYang 2017/8/7 右模糊查询
+        , RIGHT_LIKE
+        // Dong ZhaoYang 2017/8/7 小于
+        , LESS_THAN_NQ
+        // jie 2019/6/4 包含
+        , IN
+        // 不包含
+        , NOT_IN
+        // 不等于
+        ,NOT_EQUAL
+        // between
+        ,BETWEEN
+        // 不为空
+        ,NOT_NULL
+        // 为空
+        ,IS_NULL
+    }
+
+    /**
+     * @author Zheng Jie
+     * 适用于简单连接查询,复杂的请自定义该注解,或者使用sql查询
+     */
+    enum Join {
+        /** jie 2019-6-4 13:18:30 */
+        LEFT, RIGHT, INNER
+    }
+
+}
+

+ 91 - 0
eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousDeleteMapping.java

@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.zhengjie.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import me.zhengjie.annotation.AnonymousAccess;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Annotation for mapping HTTP {@code DELETE} requests onto specific handler
+ * methods.
+ * 支持匿名访问  DeleteMapping
+ *
+ * @author liaojinlong
+ * @see AnonymousGetMapping
+ * @see AnonymousPostMapping
+ * @see AnonymousPutMapping
+ * @see AnonymousPatchMapping
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.DELETE)
+public @interface AnonymousDeleteMapping {
+
+    /**
+     * Alias for {@link RequestMapping#name}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String name() default "";
+
+    /**
+     * Alias for {@link RequestMapping#value}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] produces() default {};
+
+}

+ 90 - 0
eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousGetMapping.java

@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.zhengjie.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import me.zhengjie.annotation.AnonymousAccess;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Annotation for mapping HTTP {@code GET} requests onto specific handler
+ * methods.
+ * <p>
+ * 支持匿名访问   GetMapping
+ *
+ * @author liaojinlong
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.GET)
+public @interface AnonymousGetMapping {
+
+    /**
+     * Alias for {@link RequestMapping#name}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String name() default "";
+
+    /**
+     * Alias for {@link RequestMapping#value}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes}.
+     *
+     * @since 4.3.5
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] produces() default {};
+
+}

+ 91 - 0
eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPatchMapping.java

@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.zhengjie.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import me.zhengjie.annotation.AnonymousAccess;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Annotation for mapping HTTP {@code PATCH} requests onto specific handler
+ * methods.
+ * * 支持匿名访问    PatchMapping
+ *
+ * @author liaojinlong
+ * @see AnonymousGetMapping
+ * @see AnonymousPostMapping
+ * @see AnonymousPutMapping
+ * @see AnonymousDeleteMapping
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.PATCH)
+public @interface AnonymousPatchMapping {
+
+    /**
+     * Alias for {@link RequestMapping#name}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String name() default "";
+
+    /**
+     * Alias for {@link RequestMapping#value}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] produces() default {};
+
+}

+ 91 - 0
eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPostMapping.java

@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.zhengjie.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import me.zhengjie.annotation.AnonymousAccess;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Annotation for mapping HTTP {@code POST} requests onto specific handler
+ * methods.
+ * 支持匿名访问 PostMapping
+ *
+ * @author liaojinlong
+ * @see AnonymousGetMapping
+ * @see AnonymousPostMapping
+ * @see AnonymousPutMapping
+ * @see AnonymousDeleteMapping
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.POST)
+public @interface AnonymousPostMapping {
+
+    /**
+     * Alias for {@link RequestMapping#name}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String name() default "";
+
+    /**
+     * Alias for {@link RequestMapping#value}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] produces() default {};
+
+}

+ 91 - 0
eladmin-common/src/main/java/me/zhengjie/annotation/rest/AnonymousPutMapping.java

@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.zhengjie.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import me.zhengjie.annotation.AnonymousAccess;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * Annotation for mapping HTTP {@code PUT} requests onto specific handler
+ * methods.
+ * * 支持匿名访问  PutMapping
+ *
+ * @author liaojinlong
+ * @see AnonymousGetMapping
+ * @see AnonymousPostMapping
+ * @see AnonymousPutMapping
+ * @see AnonymousDeleteMapping
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.PUT)
+public @interface AnonymousPutMapping {
+
+    /**
+     * Alias for {@link RequestMapping#name}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String name() default "";
+
+    /**
+     * Alias for {@link RequestMapping#value}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] produces() default {};
+
+}

+ 99 - 0
eladmin-common/src/main/java/me/zhengjie/aspect/LimitAspect.java

@@ -0,0 +1,99 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.aspect;
+
+import com.google.common.collect.ImmutableList;
+import me.zhengjie.annotation.Limit;
+import me.zhengjie.exception.BadRequestException;
+import me.zhengjie.utils.RequestHolder;
+import me.zhengjie.utils.StringUtils;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.core.script.RedisScript;
+import org.springframework.stereotype.Component;
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+
+/**
+ * @author /
+ */
+@Aspect
+@Component
+public class LimitAspect {
+
+    private final RedisTemplate<Object,Object> redisTemplate;
+    private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);
+
+    public LimitAspect(RedisTemplate<Object,Object> redisTemplate) {
+        this.redisTemplate = redisTemplate;
+    }
+
+    @Pointcut("@annotation(me.zhengjie.annotation.Limit)")
+    public void pointcut() {
+    }
+
+    @Around("pointcut()")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        HttpServletRequest request = RequestHolder.getHttpServletRequest();
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method signatureMethod = signature.getMethod();
+        Limit limit = signatureMethod.getAnnotation(Limit.class);
+        LimitType limitType = limit.limitType();
+        String key = limit.key();
+        if (StringUtils.isEmpty(key)) {
+            if (limitType == LimitType.IP) {
+                key = StringUtils.getIp(request);
+            } else {
+                key = signatureMethod.getName();
+            }
+        }
+
+        ImmutableList<Object> keys = ImmutableList.of(StringUtils.join(limit.prefix(), "_", key, "_", request.getRequestURI().replace("/","_")));
+
+        String luaScript = buildLuaScript();
+        RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
+        Number count = redisTemplate.execute(redisScript, keys, limit.count(), limit.period());
+        if (null != count && count.intValue() <= limit.count()) {
+            logger.info("第{}次访问key为 {},描述为 [{}] 的接口", count, keys, limit.name());
+            return joinPoint.proceed();
+        } else {
+            throw new BadRequestException("访问次数受限制");
+        }
+    }
+
+    /**
+     * 限流脚本
+     */
+    private String buildLuaScript() {
+        return "local c" +
+                "\nc = redis.call('get',KEYS[1])" +
+                "\nif c and tonumber(c) > tonumber(ARGV[1]) then" +
+                "\nreturn c;" +
+                "\nend" +
+                "\nc = redis.call('incr',KEYS[1])" +
+                "\nif tonumber(c) == 1 then" +
+                "\nredis.call('expire',KEYS[1],ARGV[2])" +
+                "\nend" +
+                "\nreturn c;";
+    }
+}

+ 27 - 0
eladmin-common/src/main/java/me/zhengjie/aspect/LimitType.java

@@ -0,0 +1,27 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.aspect;
+
+/**
+ * 限流枚举
+ * @author /
+ */
+public enum LimitType {
+    // 默认
+    CUSTOMER,
+    //  by ip addr
+    IP
+}

+ 41 - 0
eladmin-common/src/main/java/me/zhengjie/base/BaseDTO.java

@@ -0,0 +1,41 @@
+package me.zhengjie.base;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+
+/**
+ * @author Zheng Jie
+ * @date 2019年10月24日20:48:53
+ */
+@Getter
+@Setter
+public class BaseDTO  implements Serializable {
+
+    private String createBy;
+
+    private String updateBy;
+
+    private LocalDateTime createTime;
+
+    private LocalDateTime updateTime;
+
+    @Override
+    public String toString() {
+        ToStringBuilder builder = new ToStringBuilder(this);
+        Field[] fields = this.getClass().getDeclaredFields();
+        try {
+            for (Field f : fields) {
+                f.setAccessible(true);
+                builder.append(f.getName(), f.get(this)).append("\n");
+            }
+        } catch (Exception e) {
+            builder.append("toString builder encounter an error");
+        }
+        return builder.toString();
+    }
+}

+ 86 - 0
eladmin-common/src/main/java/me/zhengjie/base/BaseEntity.java

@@ -0,0 +1,86 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.base;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+import org.springframework.data.annotation.CreatedBy;
+import org.springframework.data.annotation.LastModifiedBy;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+import javax.persistence.Column;
+import javax.persistence.EntityListeners;
+import javax.persistence.MappedSuperclass;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+
+/**
+ * 通用字段, is_del 根据需求自行添加
+ * @author Zheng Jie
+ * @Date 2019年10月24日20:46:32
+ */
+@Getter
+@Setter
+@MappedSuperclass
+@EntityListeners(AuditingEntityListener.class)
+public class BaseEntity implements Serializable {
+
+    @CreatedBy
+    @Column(name = "create_by", updatable = false)
+    @ApiModelProperty(value = "创建人", hidden = true)
+    private String createBy;
+
+    @LastModifiedBy
+    @Column(name = "update_by")
+    @ApiModelProperty(value = "更新人", hidden = true)
+    private String updateBy;
+
+    @CreationTimestamp
+    @Column(name = "create_time", updatable = false)
+    @ApiModelProperty(value = "创建时间", hidden = true)
+    private LocalDateTime createTime;
+
+    @UpdateTimestamp
+    @Column(name = "update_time")
+    @ApiModelProperty(value = "更新时间", hidden = true)
+    private LocalDateTime updateTime;
+
+    /* 分组校验 */
+    public @interface Create {}
+
+    /* 分组校验 */
+    public @interface Update {}
+
+    @Override
+    public String toString() {
+        ToStringBuilder builder = new ToStringBuilder(this);
+        Field[] fields = this.getClass().getDeclaredFields();
+        try {
+            for (Field f : fields) {
+                f.setAccessible(true);
+                builder.append(f.getName(), f.get(this)).append("\n");
+            }
+        } catch (Exception e) {
+            builder.append("toString builder encounter an error");
+        }
+        return builder.toString();
+    }
+}

+ 53 - 0
eladmin-common/src/main/java/me/zhengjie/base/BaseMapper.java

@@ -0,0 +1,53 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.base;
+
+import java.util.List;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+public interface BaseMapper<D, E> {
+
+    /**
+     * DTO转Entity
+     * @param dto /
+     * @return /
+     */
+    E toEntity(D dto);
+
+    /**
+     * Entity转DTO
+     * @param entity /
+     * @return /
+     */
+    D toDto(E entity);
+
+    /**
+     * DTO集合转Entity集合
+     * @param dtoList /
+     * @return /
+     */
+    List <E> toEntity(List<D> dtoList);
+
+    /**
+     * Entity集合转DTO集合
+     * @param entityList /
+     * @return /
+     */
+    List <D> toDto(List<E> entityList);
+}

+ 165 - 0
eladmin-common/src/main/java/me/zhengjie/base/ResponseCodeConst.java

@@ -0,0 +1,165 @@
+package me.zhengjie.base;
+
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 每个业务,100个范围值就够了.
+ */
+@Slf4j
+public class ResponseCodeConst {
+
+    // 范围声明
+    static {
+        // 系统功能,从0开始,step=1000
+        ResponseCodeContainer.register(ResponseCodeConst.class, 0, 1000);
+     
+
+    }
+
+    public static final ResponseCodeConst SUCCESS = new ResponseCodeConst(1, "操作成功!", true);
+
+    public static final ResponseCodeConst ERROR_PARAM = new ResponseCodeConst(101, "参数异常!");
+
+    public static final ResponseCodeConst ERROR_PARAM_ANY = new ResponseCodeConst(102, "%s参数异常!");
+
+    public static final ResponseCodeConst SYSTEM_ERROR = new ResponseCodeConst(111, "系统错误");
+
+    public static final ResponseCodeConst DEVELOPMENT = new ResponseCodeConst(112, "此功能正在开发中");
+
+    public static final ResponseCodeConst NOT_EXISTS = new ResponseCodeConst(113, "数据不存在");
+
+    public static ResponseCodeConst REQUEST_METHOD_ERROR = new ResponseCodeConst(114, "请求方式错误");
+
+    public static ResponseCodeConst JSON_FORMAT_ERROR = new ResponseCodeConst(115, "JSON格式错误");
+
+    protected int code;
+
+    protected String msg;
+
+    protected boolean success;
+
+    public ResponseCodeConst() {
+    }
+
+    protected ResponseCodeConst(int code, String msg) {
+        super();
+        this.code = code;
+        this.msg = msg;
+        ResponseCodeContainer.put(this);
+    }
+
+    protected ResponseCodeConst(int code, String msg, boolean success) {
+        super();
+        this.code = code;
+        this.msg = msg;
+        this.success = success;
+        ResponseCodeContainer.put(this);
+    }
+
+    protected ResponseCodeConst(int code) {
+        super();
+        this.code = code;
+        ResponseCodeContainer.put(this);
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public boolean isSuccess() {
+        return success;
+    }
+
+    public void setSuccess(boolean success) {
+        this.success = success;
+    }
+
+    public static void init() {
+        log.info("ResponseCodeConst init....");
+    }
+
+    // =======================分割=======================
+
+    /**
+     * 内部类,用于检测code范围
+     *
+     * @author Anders
+     */
+    @Slf4j
+    private static class ResponseCodeContainer {
+
+        private static final Map<Integer, ResponseCodeConst> RESPONSE_CODE_MAP = new HashMap<>();
+
+        private static final Map<Class<? extends ResponseCodeConst>, int[]> RESPONSE_CODE_RANGE_MAP = new HashMap<>();
+
+        /**
+         * id的范围:[start, end]左闭右闭
+         *
+         * @param clazz
+         * @param start
+         * @param end
+         */
+        private static void register(Class<? extends ResponseCodeConst> clazz, int start, int end) {
+            if (start > end) {
+                throw new IllegalArgumentException("<ResponseDTO> start > end!");
+            }
+
+            if (RESPONSE_CODE_RANGE_MAP.containsKey(clazz)) {
+                throw new IllegalArgumentException(String.format("<ResponseDTO> Class:%s already exist!", clazz.getSimpleName()));
+            }
+            RESPONSE_CODE_RANGE_MAP.forEach((k, v) -> {
+                if ((start >= v[0] && start <= v[1]) || (end >= v[0] && end <= v[1])) {
+                    throw new IllegalArgumentException(String.format("<ResponseDTO> Class:%s 's id range[%d,%d] has " + "intersection with " + "class:%s", clazz.getSimpleName(), start, end,
+                        k.getSimpleName()));
+                }
+            });
+
+            RESPONSE_CODE_RANGE_MAP.put(clazz, new int[]{start, end});
+
+            // 提前初始化static变量,进行范围检测
+            Field[] fields = clazz.getFields();
+            if (fields.length != 0) {
+                try {
+                    fields[0].get(clazz);
+                } catch (IllegalArgumentException | IllegalAccessException e) {
+                    log.error("", e);
+                }
+            }
+        }
+
+        public static void put(ResponseCodeConst codeConst) {
+            int[] idRange = RESPONSE_CODE_RANGE_MAP.get(codeConst.getClass());
+            if (idRange == null) {
+                throw new IllegalArgumentException(String.format("<ResponseDTO> Class:%s has not been registered!", codeConst.getClass().getSimpleName()));
+            }
+            int code = codeConst.code;
+            if (code < idRange[0] || code > idRange[1]) {
+                throw new IllegalArgumentException(String.format("<ResponseDTO> Id(%d) out of range[%d,%d], " + "class:%s", code, idRange[0], idRange[1], codeConst.getClass().getSimpleName()));
+            }
+            if (RESPONSE_CODE_MAP.keySet().contains(code)) {
+                log.error(String.format("<ResponseDTO> Id(%d) out of range[%d,%d], " + "class:%s  code is repeat!", code, idRange[0], idRange[1], codeConst.getClass().getSimpleName()));
+                System.exit(0);
+            }
+            RESPONSE_CODE_MAP.put(code, codeConst);
+        }
+
+    }
+
+}

+ 139 - 0
eladmin-common/src/main/java/me/zhengjie/base/ResponseDTO.java

@@ -0,0 +1,139 @@
+package me.zhengjie.base;
+
+/**
+ * 返回类
+ *
+ * @param <T>
+ * @author zhuoda
+ */
+public class ResponseDTO<T> {
+
+	protected Integer code;
+
+	protected String msg;
+	protected String message;
+	protected Boolean success;
+
+	protected T data;
+
+	public ResponseDTO() {
+	}
+
+	public ResponseDTO(Integer code, String msg) {
+		this.code = code;
+		this.msg = msg;
+		this.success = false;
+
+	}
+
+	public ResponseDTO(Integer code, T t) {
+		this.code = code;
+		this.data = t;
+		this.success = false;
+
+	}
+
+	public ResponseDTO(ResultCode result, boolean bool) {
+		this.code = result.code();
+		this.msg = result.message();
+		this.message = result.message;
+		this.success = bool;
+	}
+
+	public ResponseDTO(ResultCode result, String msg) {
+		this.code = result.code();
+		this.msg = msg;
+		this.message = msg;
+		this.success = false;
+	}
+
+	public ResponseDTO(ResultCode result, T data, boolean bool) {
+		this.code = result.code();
+		this.msg = result.message();
+		this.message = result.message();
+		this.data = data;
+		this.success = bool;
+	}
+
+	public ResponseDTO(ResponseDTO<T> responseDTO) {
+		this.code = responseDTO.getCode();
+		this.msg = responseDTO.getMsg();
+		this.message = responseDTO.getMsg();
+		this.success = responseDTO.isSuccess();
+	}
+
+	public static <T> ResponseDTO<T> success() {
+		return new ResponseDTO<>(ResultCode.SUCCESS, true);
+	}
+
+	public static <T> ResponseDTO<T> success(T data) {
+		return new ResponseDTO<>(ResultCode.SUCCESS, data, true);
+	}
+
+	public static <T> ResponseDTO<T> error(ResultCode result) {
+
+		return new ResponseDTO<>(result, false);
+	}
+
+	public static <T> ResponseDTO<T> error(ResultCode result, String msg) {
+
+		return new ResponseDTO<>(result, msg);
+	}
+
+	public String getMsg() {
+		return msg;
+	}
+
+	public ResponseDTO<T> setMsg(String msg) {
+		this.msg = msg;
+		return this;
+	}
+
+	public int getCode() {
+		return code;
+	}
+
+	public ResponseDTO<T> setCode(Integer code) {
+		this.code = code;
+		return this;
+	}
+
+	public T getData() {
+		return data;
+	}
+
+	public ResponseDTO<T> setData(T data) {
+		this.data = data;
+		return this;
+	}
+
+	public boolean isSuccess() {
+		return success;
+	}
+
+	public void setSuccess(boolean success) {
+		this.success = success;
+	}
+
+	@Override
+	public String toString() {
+		return "ResponseDTO{" + "code=" + code + ", msg='" + msg + '\'' + ", success=" + success + ", data=" + data
+				+ '}';
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public Boolean getSuccess() {
+		return success;
+	}
+
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+	public void setSuccess(Boolean success) {
+		this.success = success;
+	}
+}

+ 186 - 0
eladmin-common/src/main/java/me/zhengjie/base/ResultCode.java

@@ -0,0 +1,186 @@
+package me.zhengjie.base;
+
+
+
+/**
+ * ========================
+ * 通用响应状态
+ * ========================
+ */
+public enum ResultCode {
+	 //这里是框架以前的
+	 REQUEST_METHOD_ERROR(114, "请求方式错误"),
+	 ERROR_PARAM (101, "参数异常!"),
+     ERROR_PARAM_ANY (102, "%s参数异常!"),
+     SYSTEM_ERROR  (111, "系统错误"),
+     DEVELOPMENT (112, "此功能正在开发中"),
+     NOT_EXISTS (113, "数据不存在"),
+     JSON_FORMAT_ERROR(115, "JSON格式错误"),
+    /* 成功状态码 */
+    SUCCESS(100,"操作成功!"),
+
+    /* 错误状态码 */
+    FAIL(-1,"操作失败!"),
+
+    /* 参数错误:10001-19999 */
+    PARAM_IS_INVALID(10001, "参数无效"),
+    PARAM_IS_BLANK(10002, "参数为空"),
+    PARAM_TYPE_BIND_ERROR(10003, "参数格式错误"),
+    PARAM_NOT_COMPLETE(10004, "参数缺失"),
+
+    /* 用户错误:20001-29999*/
+    USER_NOT_LOGGED_IN(20001, "用户未登录,请先登录"),
+    USER_LOGIN_ERROR(20002, "账号不存在或密码错误"),
+    USER_ACCOUNT_FORBIDDEN(20003, "账号已被禁用"),
+    USER_NOT_EXIST(20004, "用户不存在"),
+    USER_HAS_EXISTED(20005, "用户已存在"),
+    AUTHCODE_EXPIRATION(20006, "验证码已过期,请重试!"),
+    AUTHCODE_ERROR(20007, "验证码错误,请重试!"),
+    USER_LOGIN_PAST_DUE(20008, "登录信息已失效,请重新登陆!"),
+    LOGIN_HAS_EXISTED(20009, "该账号已登陆,请勿重复登陆!"),
+    PHONE_HAS_EXISTED(20010, "手机号码已存在!"),
+    NAME_IDCARD_UNMATCHED(20011,"姓名与身份证号不匹配"),
+
+
+    /* 业务错误:30001-39999 */
+    BUSINESS_GROUP_NO_ALLOWED_DEL(30001, "应用分组已经被应用使用,不能删除"),
+    BUSINESS_THEME_NO_ALLOWED_DEL(30002, "主题已经被用户使用,不能删除"),
+    BUSINESS_THEME_NO_ALLOWED_DISABLE(30003, "主题已经被用户使用,不能停用"),
+    BUSINESS_THEME_DEFAULT_NO_ALLOWED_DEL(30004, "默认主题,不能删除"),
+    BUSINESS_THEME_NO_ALLOWED_UPDATE(30005, "主题已经被用户使用,不能修改图片信息"),
+    BUSINESS_IMAGE_UPLOAD_ERROR(30005, "图片上传失败!"),
+    BUSINESS_PROCESS_ERROR(30006, "非法的流程合同流程!"),
+    
+    
+    BUSINESS_IS_TOP(30040, "已经到最顶部"),
+    BUSINESS_IS_BOTTOM(30041, "已经到最底部"),
+    BUSINESS_NAME_EXISTED(30051, "名称已存在"),
+    NOTARY_OFFLINE(30005, "当前公证员离线,请稍后再试!"),
+    LIVING_CERTIFICATION(30006, "请先进行活体认证"),
+    SEAT_BUSY(30007, "当前坐席忙,请稍后再试!"),
+    FACEAUTH_FAIL(30008, "活体人脸认证失败,请重试!"),
+    SIGN_FAIL(30009, "签名失败,请重试!"),
+    CREATEPDF_FAIL(30010, "PDF生成失败!"),
+    FILE_NOTFOUND_FAIL(30011, "您查看的文件未找到"),
+    SAVE_PAYINFO_FAIL(30012, "支付信息入库失败"),
+    MAIN_REJECT_FAIL(30013, "当前主工单已被驳回"),
+    RECORD_PASS_EXIST(30014, "该工单已审核通过,请勿重复操作"),
+    RECORD_REJECT_EXIST(30015, "该工单已被驳回,请勿重复操作"),
+    NOTARY_NOTE_EXIST(30016, "您已提交过笔录信息,请勿重复操作"),
+    NOTARY_NOT_PASS(30017, "工单尚未审批通过,请勿上传公证书!"),
+    ORDER_DATA_NOT_EXIST(30018, "不存在该订单!"),
+    CERTIFICATE_NO_ERROR(30019, "获取用户证书编号发生错误!"),
+    
+    LIVE_VIDEO_INVALID(30020, "活体人脸核身失败!"),
+    LIVE_VIDEO_CONVERSION(30022, "活体视频转换失败!"),
+    SIMILARITY_IS_LT_40_PERCENT (30023, "相似度低于40%,请重试!"),
+    SIGN_UPLOAD_FAIL(30024, "签字图片上传失败!"),
+    ACCOUNT_MANAGER_NO_ASSIGNED(30025, "未分配客户经理!"),
+    
+    WEBSOCKET_SEND_MSG_ERROR(30026, "websocket发送消息失败!"),
+
+    FILE_DOES_NOT_EXIST(30027, "文件不存在!"),
+    
+    MSG_SEND_FREQUENT(30028, "短信发送太频繁!"),
+    MSG_SEND_FAIL(30029, "短信发送失败!"),
+    
+    FILE_UPLOAD_FAIL(30030, "文件上传失败!"),
+
+    AXQ_USER_REGIST_FAIL(30031,"安心签注册失败"),
+    AXQ_AUTH_MESSAGE_FAIL(30032,"授权信息发送失败"),
+    AXQ_AUTH_FAIL(30033,"签名授权失败"),
+    HANDWRITING_FAIL(30034,"签名与本人姓名不一致"),
+    AXQ_PROJECT_AUTHED(30035,"项目已经授权"),
+    AXQ_PROJECT_UNAUTHED(30036,"项目未授权"),
+    UNFINISHED_ORDER_FAIL(30037,"尚有未完成订单"),
+    HAS_BEEN_MAPPED_FAIL(30038,"提交银行已有关联的公证处,请核对信息是否正确!"),
+    SUB_ORDER_UNCOMPLETED(30039,"子订单信息尚未提交完整!"),
+    INTERVIEW_NOTE_UNSUBMIT(30040, "面谈笔录尚未完成!"),
+    UPLOAD_SEAL_FAIL(30041, "上传签章失败"),
+    QUERY_CERTIFICATE_FAIL(30042,"查询证书失败!"),
+    REMAIN_BANK_USER(30043, "银行下尚有用户未删除!"),
+    REMAIN_NOTARY_USER(30044, "公证处尚有用户未删除!"),
+    REMAIN_NOTARY_OFFICE_MAPPING(30045,"银行尚有关联的公证处未取消!"),
+    REMAIN_USER_BANK_MAPPING(30046,"公证处尚有关联的银行未取消!"),
+    MODEL_ALREADY_EXISGT(30047,"%s,该类模板已经创建!"),
+    MODEL_SORT_EXISTED(30048,"模板排序值重复!"),
+    NO_NOTARIZARION_NO(30049,"没有可用的公证书编号!"),
+    SUCC_ALREADY_EXISTED(30050,"社会统一信用代码已存在,请确认输入是否正确!"),
+    SUCC_NEED_INPUT(30051,"请输入社会统一信用代码!"),
+    ORDER_OCR_SERVICE_UNAVAILABLE(30052,"订单扫描录入功能尚未开通,请联系平台开通后使用!"),
+    JOB_NO_ALREADY_EXISTS(30053,"此工号已存在,请重新输入!"),
+    NOTARY_OFFICE_CODE_ALREADY_EXISTS(30054,"公证处编码已存在!"),
+    /* 系统错误:40001-49999 */
+    SYSTEM_INNER_ERROR(40001, "系统繁忙,请稍后重试"),
+    UPLOAD_ERROR(40002, "系统异常,上传文件失败"),
+    FILE_MAX_SIZE_OVERFLOW(40003, "上传尺寸过大"),
+    FILE_ACCEPT_NOT_SUPPORT(40004, "上传文件格式不支持"),
+    SET_UP_AT_LEAST_ONE_ADMIN(40005, "至少指定一个管理员"),
+    URL_INVALID(40006, "地址不合法"),
+    FILE_EXISTING(40017,"上传的文件已存在"),
+    LINK_AND_LOGOUT_NO_MATCH(40006, "主页地址和注销地址IP不一致"),
+    IP_AND_PORT_EXISTED(40007, "当前IP和端口已经被占中"),
+    LINK_IS_REQUIRED(40008, "生成第三方token认证信息: 主页地址不能为空,请完善信息"),
+    ONLY_ROOT_DEPARTMENT(40009, "组织机构只能存在一个根机构"),
+    DEPART_CODE_EXISTED(40010, "组织机构编码已存在"),
+    DEPART_CONTAINS_USERS(40011, "该机构下是存在用户,不允许删除"),
+    DEPART_CONTAINS_SON(40012, "该机构下是存在子级机构,不允许删除"),
+    DEPART_PARENT_IS_SELF(40013, "选择的父机构不能为本身"),
+    DICT_EXIST_DEPEND(40014, "该字典数据存在详情依赖,不允许删除"),
+    DICT_DETAIL_LOCK(40015, "该字典数据被锁定,不允许修改或删除"),
+    DEPART_CODE_EXISTED_WITH_ARGS(40016, "组织机构编码【{0}】系统已存在"),
+    IMAGE_NOT_STANDARD(40017,"请保持人脸居中"),
+    SINGLE_PERSON(40018,"镜头内只能有1人"),
+    /* 数据错误:50001-599999 */
+    RESULT_DATA_NONE(50001, "数据未找到"),
+    DATA_IS_WRONG(50002, "数据有误"),
+    DATA_ALREADY_EXISTED(50003, "数据已存在"),
+    DATA_INSERT_ERROR(50003,"数据插入失败"),
+    DATA_UPDATE_ERROR(50004,"数据编辑失败"),
+
+    /* 接口错误:60001-69999 */
+    INTERFACE_INNER_INVOKE_ERROR(60001, "内部系统接口调用异常"),
+    INTERFACE_OUTTER_INVOKE_ERROR(60002, "外部系统接口调用异常"),
+    INTERFACE_FORBID_VISIT(60003, "该接口禁止访问"),
+    INTERFACE_ADDRESS_INVALID(60004, "接口地址无效"),
+    INTERFACE_REQUEST_TIMEOUT(60005, "接口请求超时"),
+    INTERFACE_EXCEED_LOAD(60006, "接口负载过高"),
+    INTERFACE_ILLEGAL_REQUEST(60007, "非法的请求"),
+    INTERFACE_SMS_TEMPLATE(60008, "模板请求错误"),
+    /* 权限错误:70001-79999 */
+    PERMISSION_UNAUTHENTICATED(70001,"此操作需要登陆系统!"),
+    PERMISSION_UNAUTHORISE(70002,"权限不足,无权操作!"),
+    PERMISSION_EXPIRE(70003,"登录状态过期!"),
+    PERMISSION_TOKEN_EXPIRED(70004, "token已过期"),
+    PERMISSION_LIMIT(70005, "访问次数受限制"),
+    PERMISSION_TOKEN_INVALID(70006, "无效token"),
+    PERMISSION_SIGNATURE_ERROR(70007, "签名失败"),
+    
+    TOKEN_ILLEGAL(7008,"违法Authorization,服务端不存在信息,请重新鉴权获取!"),
+    ;
+ 
+    //操作代码
+    int code;
+    //提示信息
+    String message;
+    ResultCode(int code, String message){
+        this.code = code;
+        this.message = message;
+    }
+
+    public int code() {
+        return code;
+    }
+
+    public String message() {
+        return message;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+}

+ 45 - 0
eladmin-common/src/main/java/me/zhengjie/config/AuditorConfig.java

@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.config;
+
+import me.zhengjie.utils.SecurityUtils;
+import org.springframework.data.domain.AuditorAware;
+import org.springframework.stereotype.Component;
+import java.util.Optional;
+
+/**
+ * @description  : 设置审计
+ * @author  : Dong ZhaoYang
+ * @date : 2019/10/28
+ */
+@Component("auditorAware")
+public class AuditorConfig implements AuditorAware<String> {
+
+    /**
+     * 返回操作员标志信息
+     *
+     * @return /
+     */
+    @Override
+    public Optional<String> getCurrentAuditor() {
+        try {
+            // 这里应根据实际业务情况获取具体信息
+            return Optional.of(SecurityUtils.getCurrentUsername());
+        }catch (Exception ignored){}
+        // 用户定时任务,或者无Token调用的情况
+        return Optional.of("System");
+    }
+}

+ 38 - 0
eladmin-common/src/main/java/me/zhengjie/config/ElAdminProperties.java

@@ -0,0 +1,38 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.config;
+
+import lombok.Data;
+import me.zhengjie.utils.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Zheng Jie
+ * @description
+ * @date 2021-11-22
+ **/
+@Data
+@Component
+public class ElAdminProperties {
+
+    public static Boolean ipLocal;
+
+    @Value("${ip.local-parsing}")
+    public void setIpLocal(Boolean ipLocal) {
+        ElAdminProperties.ipLocal = ipLocal;
+    }
+}

+ 37 - 0
eladmin-common/src/main/java/me/zhengjie/config/ElPermissionConfig.java

@@ -0,0 +1,37 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.config;
+
+import me.zhengjie.utils.SecurityUtils;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.stereotype.Service;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Zheng Jie
+ */
+@Service(value = "el")
+public class ElPermissionConfig {
+
+    public Boolean check(String ...permissions){
+        // 获取当前用户的所有权限
+        List<String> elPermissions = SecurityUtils.getCurrentUser().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
+        // 判断当前用户的所有权限是否包含接口上定义的权限
+        return elPermissions.contains("admin") || Arrays.stream(permissions).anyMatch(elPermissions::contains);
+    }
+}

+ 60 - 0
eladmin-common/src/main/java/me/zhengjie/config/FileProperties.java

@@ -0,0 +1,60 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.config;
+
+import lombok.Data;
+import me.zhengjie.utils.ElAdminConstant;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Zheng Jie
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "file")
+public class FileProperties {
+
+    /** 文件大小限制 */
+    private Long maxSize;
+
+    /** 头像大小限制 */
+    private Long avatarMaxSize;
+
+    private ElPath mac;
+
+    private ElPath linux;
+
+    private ElPath windows;
+
+    public ElPath getPath(){
+        String os = System.getProperty("os.name");
+        if(os.toLowerCase().startsWith(ElAdminConstant.WIN)) {
+            return windows;
+        } else if(os.toLowerCase().startsWith(ElAdminConstant.MAC)){
+            return mac;
+        }
+        return linux;
+    }
+
+    @Data
+    public static class ElPath{
+
+        private String path;
+
+        private String avatar;
+    }
+}

+ 217 - 0
eladmin-common/src/main/java/me/zhengjie/config/RedisConfig.java

@@ -0,0 +1,217 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.config;
+
+import cn.hutool.core.lang.Assert;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.parser.ParserConfig;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cache.Cache;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.interceptor.CacheErrorHandler;
+import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import reactor.util.annotation.Nullable;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-24
+ */
+@Slf4j
+@Configuration
+@EnableCaching
+@ConditionalOnClass(RedisOperations.class)
+@EnableConfigurationProperties(RedisProperties.class)
+public class RedisConfig extends CachingConfigurerSupport {
+
+    /**
+     *  设置 redis 数据默认过期时间,默认2小时
+     *  设置//@Cacheable 序列化方式
+     */
+    @Bean
+    public RedisCacheConfiguration redisCacheConfiguration(){
+        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
+        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
+        configuration = configuration.serializeValuesWith(RedisSerializationContext.
+                SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofHours(2));
+        return configuration;
+    }
+
+    @SuppressWarnings("all")
+    @Bean(name = "redisTemplate")
+    @ConditionalOnMissingBean(name = "redisTemplate")
+    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisTemplate<Object, Object> template = new RedisTemplate<>();
+        //序列化
+        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
+        // value值的序列化采用fastJsonRedisSerializer
+        template.setValueSerializer(fastJsonRedisSerializer);
+        template.setHashValueSerializer(fastJsonRedisSerializer);
+        // 全局开启AutoType,这里方便开发,使用全局的方式
+        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
+        // 建议使用这种方式,小范围指定白名单
+        ParserConfig.getGlobalInstance().addAccept("me.zhengjie.");
+        // key的序列化采用StringRedisSerializer
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setConnectionFactory(redisConnectionFactory);
+        return template;
+    }
+
+    /**
+     * 自定义缓存key生成策略,默认将使用该策略
+     */
+    @Bean
+    @Override
+    public KeyGenerator keyGenerator() {
+        return (target, method, params) -> {
+            Map<String,Object> container = new HashMap<>(4);
+            Class<?> targetClassClass = target.getClass();
+            // 类地址
+            container.put("class",targetClassClass.toGenericString());
+            // 方法名称
+            container.put("methodName",method.getName());
+            // 包名称
+            container.put("package",targetClassClass.getPackage());
+            // 参数列表
+            for (int i = 0; i < params.length; i++) {
+                container.put(String.valueOf(i),params[i]);
+            }
+            // 转为JSON字符串
+            String jsonString = JSON.toJSONString(container);
+            // 做SHA256 Hash计算,得到一个SHA256摘要作为Key
+            return DigestUtils.sha256Hex(jsonString);
+        };
+    }
+
+    @Bean
+    @Override
+    public CacheErrorHandler errorHandler() {
+        // 异常处理,当Redis发生异常时,打印日志,但是程序正常走
+        log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
+        return new CacheErrorHandler() {
+            @Override
+            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
+                log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
+            }
+
+            @Override
+            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
+                log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
+            }
+
+            @Override
+            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
+                log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
+            }
+
+            @Override
+            public void handleCacheClearError(RuntimeException e, Cache cache) {
+                log.error("Redis occur handleCacheClearError:", e);
+            }
+        };
+    }
+
+}
+
+/**
+ * Value 序列化
+ *
+ * @author /
+ * @param <T>
+ */
+ class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
+
+    private final Class<T> clazz;
+
+    FastJsonRedisSerializer(Class<T> clazz) {
+        super();
+        this.clazz = clazz;
+    }
+
+    @Override
+    public byte[] serialize(T t) {
+        if (t == null) {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public T deserialize(byte[] bytes) {
+        if (bytes == null || bytes.length <= 0) {
+            return null;
+        }
+        String str = new String(bytes, StandardCharsets.UTF_8);
+        return JSON.parseObject(str, clazz);
+    }
+
+}
+
+/**
+ * 重写序列化器
+ *
+ * @author /
+ */
+class StringRedisSerializer implements RedisSerializer<Object> {
+
+    private final Charset charset;
+
+    StringRedisSerializer() {
+        this(StandardCharsets.UTF_8);
+    }
+
+    private StringRedisSerializer(Charset charset) {
+        Assert.notNull(charset, "Charset must not be null!");
+        this.charset = charset;
+    }
+
+    @Override
+    public String deserialize(byte[] bytes) {
+        return (bytes == null ? null : new String(bytes, charset));
+    }
+
+	@Override
+	public @Nullable byte[] serialize(Object object) {
+		String string = JSON.toJSONString(object);
+
+		if (org.apache.commons.lang3.StringUtils.isBlank(string)) {
+			return null;
+		}
+		string = string.replace("\"", "");
+		return string.getBytes(charset);
+	}
+}

+ 38 - 0
eladmin-common/src/main/java/me/zhengjie/config/RsaProperties.java

@@ -0,0 +1,38 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.config;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Zheng Jie
+ * @website https://el-admin.vip
+ * @description
+ * @date 2020-05-18
+ **/
+@Data
+@Component
+public class RsaProperties {
+
+    public static String privateKey;
+
+    @Value("${rsa.private_key}")
+    public void setPrivateKey(String privateKey) {
+        RsaProperties.privateKey = privateKey;
+    }
+}

+ 148 - 0
eladmin-common/src/main/java/me/zhengjie/config/SwaggerConfig.java

@@ -0,0 +1,148 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.config;
+
+import com.fasterxml.classmate.TypeResolver;
+import com.google.common.base.Predicates;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.data.domain.Pageable;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.schema.AlternateTypeRule;
+import springfox.documentation.schema.AlternateTypeRuleConvention;
+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.EnableSwagger2;
+import java.util.ArrayList;
+import java.util.List;
+import static com.google.common.collect.Lists.newArrayList;
+import static springfox.documentation.schema.AlternateTypeRules.newRule;
+
+/**
+ * api页面 /doc.html
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+@Configuration
+@EnableSwagger2
+public class SwaggerConfig {
+
+    @Value("${jwt.header}")
+    private String tokenHeader;
+
+    @Value("${swagger.enabled}")
+    private Boolean enabled;
+
+    @Bean
+    @SuppressWarnings("all")
+    public Docket createRestApi() {
+        return new Docket(DocumentationType.SWAGGER_2)
+                .enable(enabled)
+                .pathMapping("/")
+                .apiInfo(apiInfo())
+                .select()
+                .paths(Predicates.not(PathSelectors.regex("/error.*")))
+                .paths(PathSelectors.any())
+                .build()
+                //添加登陆认证
+                .securitySchemes(securitySchemes())
+                .securityContexts(securityContexts());
+    }
+
+    private ApiInfo apiInfo() {
+        return new ApiInfoBuilder()
+                .description("一个简单且易上手的 Spring boot 后台管理框架")
+                .title("EL-ADMIN 接口文档")
+                .version("2.6")
+                .build();
+    }
+
+    private List<SecurityScheme> securitySchemes() {
+        //设置请求头信息
+        List<SecurityScheme> securitySchemes = new ArrayList<>();
+        ApiKey apiKey = new ApiKey(tokenHeader, tokenHeader, "header");
+        securitySchemes.add(apiKey);
+        return securitySchemes;
+    }
+
+    private List<SecurityContext> securityContexts() {
+        //设置需要登录认证的路径
+        List<SecurityContext> securityContexts = new ArrayList<>();
+        // ^(?!auth).*$ 表示所有包含auth的接口不需要使用securitySchemes即不需要带token
+        // ^标识开始  ()里是一子表达式  ?!/auth表示匹配不是/auth的位置,匹配上则添加请求头,注意路径已/开头  .表示任意字符  *表示前面的字符匹配多次 $标识结束
+        securityContexts.add(getContextByPath());
+        return securityContexts;
+    }
+
+    private SecurityContext getContextByPath() {
+        return SecurityContext.builder()
+                .securityReferences(defaultAuth())
+                .forPaths(PathSelectors.regex("^(?!/auth).*$"))
+                .build();
+    }
+
+    private List<SecurityReference> defaultAuth() {
+        List<SecurityReference> securityReferences = new ArrayList<>();
+        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
+        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
+        authorizationScopes[0] = authorizationScope;
+        securityReferences.add(new SecurityReference(tokenHeader, authorizationScopes));
+        return securityReferences;
+    }
+}
+
+/**
+ *  将Pageable转换展示在swagger中
+ */
+@Configuration
+class SwaggerDataConfig {
+
+    @Bean
+    public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) {
+        return new AlternateTypeRuleConvention() {
+            @Override
+            public int getOrder() {
+                return Ordered.HIGHEST_PRECEDENCE;
+            }
+
+            @Override
+            public List<AlternateTypeRule> rules() {
+                return newArrayList(newRule(resolver.resolve(Pageable.class), resolver.resolve(Page.class)));
+            }
+        };
+    }
+
+    @ApiModel
+    @Data
+    private static class Page {
+        @ApiModelProperty("页码 (0..N)")
+        private Integer page;
+
+        @ApiModelProperty("每页显示的数目")
+        private Integer size;
+
+        @ApiModelProperty("以下列格式排序标准:property[,asc | desc]。 默认排序顺序为升序。 支持多种排序条件:如:id,asc")
+        private List<String> sort;
+    }
+}

+ 14 - 0
eladmin-common/src/main/java/me/zhengjie/exception/AccountNoActivationException.java

@@ -0,0 +1,14 @@
+package me.zhengjie.exception;
+
+public class AccountNoActivationException extends RuntimeException {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	public AccountNoActivationException(String msg) {
+		super(msg);
+	}
+
+}

+ 98 - 0
eladmin-common/src/main/java/me/zhengjie/exception/BadConfigurationException.java

@@ -0,0 +1,98 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package me.zhengjie.exception;
+
+/**
+ * 统一关于错误配置信息 异常
+ *
+ * @author: liaojinlong
+ * @date: 2020/6/10 18:06
+ */
+public class BadConfigurationException extends RuntimeException {
+    /**
+     * Constructs a new runtime exception with {@code null} as its
+     * detail message.  The cause is not initialized, and may subsequently be
+     * initialized by a call to {@link #initCause}.
+     */
+    public BadConfigurationException() {
+        super();
+    }
+
+    /**
+     * Constructs a new runtime exception with the specified detail message.
+     * The cause is not initialized, and may subsequently be initialized by a
+     * call to {@link #initCause}.
+     *
+     * @param message the detail message. The detail message is saved for
+     *                later retrieval by the {@link #getMessage()} method.
+     */
+    public BadConfigurationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new runtime exception with the specified detail message and
+     * cause.  <p>Note that the detail message associated with
+     * {@code cause} is <i>not</i> automatically incorporated in
+     * this runtime exception's detail message.
+     *
+     * @param message the detail message (which is saved for later retrieval
+     *                by the {@link #getMessage()} method).
+     * @param cause   the cause (which is saved for later retrieval by the
+     *                {@link #getCause()} method).  (A {@code null} value is
+     *                permitted, and indicates that the cause is nonexistent or
+     *                unknown.)
+     * @since 1.4
+     */
+    public BadConfigurationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructs a new runtime exception with the specified cause and a
+     * detail message of {@code (cause==null ? null : cause.toString())}
+     * (which typically contains the class and detail message of
+     * {@code cause}).  This constructor is useful for runtime exceptions
+     * that are little more than wrappers for other throwables.
+     *
+     * @param cause the cause (which is saved for later retrieval by the
+     *              {@link #getCause()} method).  (A {@code null} value is
+     *              permitted, and indicates that the cause is nonexistent or
+     *              unknown.)
+     * @since 1.4
+     */
+    public BadConfigurationException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new runtime exception with the specified detail
+     * message, cause, suppression enabled or disabled, and writable
+     * stack trace enabled or disabled.
+     *
+     * @param message            the detail message.
+     * @param cause              the cause.  (A {@code null} value is permitted,
+     *                           and indicates that the cause is nonexistent or unknown.)
+     * @param enableSuppression  whether or not suppression is enabled
+     *                           or disabled
+     * @param writableStackTrace whether or not the stack trace should
+     *                           be writable
+     * @since 1.7
+     */
+    protected BadConfigurationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}

+ 40 - 0
eladmin-common/src/main/java/me/zhengjie/exception/BadRequestException.java

@@ -0,0 +1,40 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.exception;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ * 统一异常处理
+ */
+@Getter
+public class BadRequestException extends RuntimeException{
+
+    private Integer status = BAD_REQUEST.value();
+
+    public BadRequestException(String msg){
+        super(msg);
+    }
+
+    public BadRequestException(HttpStatus status,String msg){
+        super(msg);
+        this.status = status.value();
+    }
+}

+ 43 - 0
eladmin-common/src/main/java/me/zhengjie/exception/EntityExistException.java

@@ -0,0 +1,43 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.exception;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+public class EntityExistException extends RuntimeException {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 6320246473939326851L;
+
+	public EntityExistException(String simpleName, String field, String val) {
+		super(EntityExistException.generateMessage(simpleName, field, val));
+	}
+
+	public EntityExistException(Class<?> clazz, String field, String val) {
+		super(EntityExistException.generateMessage(clazz.getSimpleName(), field, val));
+	}
+
+	private static String generateMessage(String entity, String field, String val) {
+		return StringUtils.capitalize(entity) + "" + field + "\"" + val + "\"存在";
+		
+	}
+}

+ 34 - 0
eladmin-common/src/main/java/me/zhengjie/exception/EntityNotFoundException.java

@@ -0,0 +1,34 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.exception;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+public class EntityNotFoundException extends RuntimeException {
+
+    public EntityNotFoundException(Class clazz, String field, String val) {
+        super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), field, val));
+    }
+
+    private static String generateMessage(String entity, String field, String val) {
+        return StringUtils.capitalize(entity)
+                + " with " + field + " "+ val + " does not exist";
+    }
+}

+ 52 - 0
eladmin-common/src/main/java/me/zhengjie/exception/handler/ApiError.java

@@ -0,0 +1,52 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.exception.handler;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+@Data
+class ApiError {
+
+    private Integer status = 400;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime timestamp;
+    private String message;
+
+    private ApiError() {
+        timestamp = LocalDateTime.now();
+    }
+
+    public static ApiError error(String message){
+        ApiError apiError = new ApiError();
+        apiError.setMessage(message);
+        return apiError;
+    }
+
+    public static ApiError error(Integer status, String message){
+        ApiError apiError = new ApiError();
+        apiError.setStatus(status);
+        apiError.setMessage(message);
+        return apiError;
+    }
+}
+
+

+ 209 - 0
eladmin-common/src/main/java/me/zhengjie/exception/handler/GlobalExceptionHandler.java

@@ -0,0 +1,209 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.exception.handler;
+
+import static org.springframework.http.HttpStatus.NOT_FOUND;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+
+import org.springframework.beans.TypeMismatchException;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.InternalAuthenticationServiceException;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import lombok.extern.slf4j.Slf4j;
+import me.zhengjie.base.ResponseCodeConst;
+import me.zhengjie.base.ResponseDTO;
+import me.zhengjie.base.ResultCode;
+import me.zhengjie.exception.AccountNoActivationException;
+import me.zhengjie.exception.BadRequestException;
+import me.zhengjie.exception.EntityExistException;
+import me.zhengjie.exception.EntityNotFoundException;
+import me.zhengjie.utils.ThrowableUtil;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+	/**
+	 * 添加全局异常处理流程
+	 *
+	 * @param e
+	 * @return
+	 * @throws Exception
+	 */
+	@ResponseBody
+	@ExceptionHandler(Exception.class)
+	public ResponseDTO<?> exceptionHandler(Exception e) {
+		log.error("error: {} Cause {} ", e.getMessage(), e.getCause());
+		e.printStackTrace();
+		// http 请求方式错误
+		if (e instanceof HttpRequestMethodNotSupportedException) {
+			return ResponseDTO.error(ResultCode.REQUEST_METHOD_ERROR);
+		}
+
+		// 参数类型错误
+		if (e instanceof TypeMismatchException) {
+			return ResponseDTO.error(ResultCode.ERROR_PARAM);
+		}
+
+		// 账号没有激活
+		if (e instanceof AccountNoActivationException) {
+			return ResponseDTO.error(ResultCode.USER_ACCOUNT_FORBIDDEN);
+		}
+		// json 格式错误
+		if (e instanceof HttpMessageNotReadableException) {
+			return ResponseDTO.error(ResultCode.JSON_FORMAT_ERROR);
+		}
+		if (e instanceof UsernameNotFoundException) {
+			return ResponseDTO.error(ResultCode.USER_LOGIN_ERROR);
+		}
+		if (e instanceof InternalAuthenticationServiceException) {
+			InternalAuthenticationServiceException error = (InternalAuthenticationServiceException) e;
+			String message = error.getMessage();
+			if (message != null && message.contains("UsernameNotFoundException")) {
+				return ResponseDTO.error(ResultCode.USER_LOGIN_ERROR);
+			}
+			Throwable t = e.getCause();
+			if (t instanceof AccountNoActivationException) {
+				return ResponseDTO.error(ResultCode.USER_ACCOUNT_FORBIDDEN);
+			}
+
+		}
+		// 参数校验未通过
+		if (e instanceof MethodArgumentNotValidException) {
+			List<FieldError> fieldErrors = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors();
+			List<String> msgList = fieldErrors.stream().map(FieldError::getDefaultMessage).collect(Collectors.toList());
+			return ResponseDTO.error(ResultCode.ERROR_PARAM, String.join(",", msgList));
+		}
+		// 绑定错误
+		if (e instanceof BindException) {
+			List<FieldError> fieldErrors = ((BindException) e).getBindingResult().getFieldErrors();
+			List<String> msgList = fieldErrors.stream().map(FieldError::getDefaultMessage).collect(Collectors.toList());
+			return ResponseDTO.error(ResultCode.ERROR_PARAM, String.join(",", msgList));
+		}
+		if (e instanceof ConstraintViolationException) {
+//			List<FieldError> fieldErrors = ((ConstraintViolationException) e).getBindingResult().getFieldErrors();
+//			List<String> msgList = fieldErrors.stream().map(FieldError::getDefaultMessage).collect(Collectors.toList());
+//			return ResponseDTO.error(ResultCode.ERROR_PARAM, String.join(",", msgList));
+			ConstraintViolationException exception = (ConstraintViolationException) e;
+
+			Set<ConstraintViolation<?>> violations = exception.getConstraintViolations();
+			StringBuilder builder = new StringBuilder();
+			for (ConstraintViolation<?> violation : violations) {
+				builder.append(violation.getMessage());
+				break;
+			}
+			// return ServerResponse.createByErrorMessage(builder.toString());
+			return ResponseDTO.error(ResultCode.ERROR_PARAM, builder.toString());
+
+		}
+
+		return ResponseDTO.error(ResultCode.SYSTEM_ERROR);
+	}
+
+	/**
+	 * 处理所有不可知的异常
+	 */
+	@ExceptionHandler(Throwable.class)
+	public ResponseDTO<ApiError> handleException(Throwable e) {
+		// 打印堆栈信息
+		log.error(ThrowableUtil.getStackTrace(e));
+		return buildResponseEntity(ApiError.error(e.getMessage()));
+	}
+
+	/**
+	 * BadCredentialsException
+	 */
+	@ExceptionHandler(BadCredentialsException.class)
+	public ResponseDTO<ApiError> badCredentialsException(BadCredentialsException e) {
+		// 打印堆栈信息
+		String message = "坏的凭证".equals(e.getMessage()) ? "用户名或密码不正确" : e.getMessage();
+		log.error(message);
+		return buildResponseEntity(ApiError.error(message));
+	}
+
+	/**
+	 * 处理自定义异常
+	 */
+	@ExceptionHandler(value = BadRequestException.class)
+	public ResponseDTO<ApiError> badRequestException(BadRequestException e) {
+		// 打印堆栈信息
+		log.error("异常信息" + e.getLocalizedMessage());
+		return buildResponseEntity(ApiError.error(e.getStatus(), e.getMessage()));
+	}
+
+	/**
+	 * 处理 EntityExist
+	 */
+	@ExceptionHandler(value = EntityExistException.class)
+	public ResponseDTO<ApiError> entityExistException(EntityExistException e) {
+		// 打印堆栈信息
+//		log.error(ThrowableUtil.getStackTrace(e));
+		return buildResponseEntity(ApiError.error(e.getMessage()));
+	}
+
+	/**
+	 * 处理 EntityNotFound
+	 */
+	@ExceptionHandler(value = EntityNotFoundException.class)
+	public ResponseDTO<ApiError> entityNotFoundException(EntityNotFoundException e) {
+		// 打印堆栈信息
+//		log.error(ThrowableUtil.getStackTrace(e));
+		return buildResponseEntity(ApiError.error(NOT_FOUND.value(), e.getMessage()));
+	}
+
+	/**
+	 * 处理所有接口数据验证异常
+	 */
+	@ExceptionHandler(MethodArgumentNotValidException.class)
+	public ResponseDTO<ApiError> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+		// 打印堆栈信息
+		log.error(ThrowableUtil.getStackTrace(e));
+		String[] str = Objects.requireNonNull(e.getBindingResult().getAllErrors().get(0).getCodes())[1].split("\\.");
+		String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
+		String msg = "不能为空";
+		if (msg.equals(message)) {
+			message = str[1] + ":" + message;
+		}
+		return buildResponseEntity(ApiError.error(message));
+	}
+
+	/**
+	 * 统一返回
+	 */
+	private ResponseDTO<ApiError> buildResponseEntity(ApiError apiError) {
+		return new ResponseDTO<>(apiError.getStatus(), apiError.getMessage());
+	}
+}

+ 58 - 0
eladmin-common/src/main/java/me/zhengjie/utils/CacheKey.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package me.zhengjie.utils;
+
+/**
+ * @author: liaojinlong
+ * @date: 2020/6/11 15:49
+ * @apiNote: 关于缓存的Key集合
+ */
+public interface CacheKey {
+
+    /**
+     * 用户
+     */
+    String USER_ID = "user::id:";
+    /**
+     * 数据
+     */
+    String DATA_USER = "data::user:";
+    /**
+     * 菜单
+     */
+    String MENU_ID = "menu::id:";
+    String MENU_USER = "menu::user:";
+    /**
+     * 角色授权
+     */
+    String ROLE_AUTH = "role::auth:";
+    /**
+     * 角色信息
+     */
+    String ROLE_ID = "role::id:";
+    /**
+     * 部门
+     */
+    String DEPT_ID = "dept::id:";
+    /**
+     * 岗位
+     */
+    String JOB_ID = "job::id:";
+    /**
+     * 数据字典
+     */
+    String DICT_NAME = "dict::name:";
+}

+ 43 - 0
eladmin-common/src/main/java/me/zhengjie/utils/CallBack.java

@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019-2020  the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.zhengjie.utils;
+
+/**
+ * @author: liaojinlong
+ * @date: 2020/6/9 17:02
+ * @since: 1.0
+ * @see {@link SpringContextHolder}
+ * 针对某些初始化方法,在SpringContextHolder 初始化前时,<br>
+ * 可提交一个 提交回调任务。<br>
+ * 在SpringContextHolder 初始化后,进行回调使用
+ */
+
+public interface CallBack {
+    /**
+     * 回调执行方法
+     */
+    void executor();
+
+    /**
+     * 本回调任务名称
+     * @return /
+     */
+    default String getCallBackName() {
+        return Thread.currentThread().getId() + ":" + this.getClass().getName();
+    }
+}
+

+ 65 - 0
eladmin-common/src/main/java/me/zhengjie/utils/ClientCache.java

@@ -0,0 +1,65 @@
+package me.zhengjie.utils;
+
+import com.corundumstudio.socketio.SocketIOClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author litong
+ * @date 2019/11/6 16:01
+ */
+//@Component
+public class ClientCache {
+
+	/**
+	 * 本地缓存
+	 */
+	private static Map<String, HashMap<UUID, SocketIOClient>> concurrentHashMap = new ConcurrentHashMap<>();
+
+	/**
+	 * 存入本地缓存
+	 *
+	 * @param sname         用户ID
+	 * @param sessionId      页面sessionID
+	 * @param socketIOClient 页面对应的通道连接信息
+	 */
+	public void saveClient(String sname, UUID sessionId, SocketIOClient socketIOClient) {
+		HashMap<UUID, SocketIOClient> sessionIdClientCache = concurrentHashMap.get(sname);
+		if (sessionIdClientCache == null) {
+			sessionIdClientCache = new HashMap<>();
+		}
+		// 先判断是否存在
+		if (concurrentHashMap.containsKey(sname)) {
+			concurrentHashMap.remove(sname);
+		}
+		sessionIdClientCache.put(sessionId, socketIOClient);
+		concurrentHashMap.put(sname, sessionIdClientCache);
+	}
+
+	/**
+	 * 根据用户ID获取所有通道信息
+	 *
+	 * @param sname
+	 * @return
+	 */
+	public HashMap<UUID, SocketIOClient> getUserClient(String sname) {
+		return concurrentHashMap.get(sname);
+	}
+
+	public static Map<String, HashMap<UUID, SocketIOClient>> getWebSocketMap() {
+		return concurrentHashMap;
+	}
+
+	/**
+	 * 根据用户ID及页面sessionID删除页面链接信息
+	 *
+	 * @param userId
+	 */
+	public void deleteSessionClient(String userId) {
+		concurrentHashMap.remove(userId);
+	}
+}

+ 47 - 0
eladmin-common/src/main/java/me/zhengjie/utils/CloseUtil.java

@@ -0,0 +1,47 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import java.io.Closeable;
+
+/**
+ * @author Zheng Jie
+ * @website https://el-admin.vip
+ * @description 用于关闭各种连接,缺啥补啥
+ * @date 2021-03-05
+ **/
+public class CloseUtil {
+
+    public static void close(Closeable closeable) {
+        if (null != closeable) {
+            try {
+                closeable.close();
+            } catch (Exception e) {
+                // 静默关闭
+            }
+        }
+    }
+
+    public static void close(AutoCloseable closeable) {
+        if (null != closeable) {
+            try {
+                closeable.close();
+            } catch (Exception e) {
+                // 静默关闭
+            }
+        }
+    }
+}

+ 301 - 0
eladmin-common/src/main/java/me/zhengjie/utils/DateFormatUtils.java

@@ -0,0 +1,301 @@
+package me.zhengjie.utils;
+
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 日期处理
+ * 
+ * @author hui.deng
+ * 
+ * @date 2016年12月21日 下午12:53:33
+ */
+public class DateFormatUtils {
+	/** 时间格式(yyyy-MM-dd) */
+	public final static String DATE_PATTERN = "yyyy-MM-dd";
+	public final static String DATE_PATTERN2 = "yyyy/MM/dd";
+	public final static String DATE_PATTERN3 = "yyyy.MM.dd";
+	public final static String DATE_PATTERN4 = "yyyyMMdd";
+
+	public final static String DATE_PATTERN6 = "yyyy/M/dd";
+	public final static String DATE_PATTERN7 = "yyyy/MM/d";
+	public final static String DATE_PATTERN8 = "yyyy/M/d";
+	/** 时间格式(yyyy-MM-dd HH:mm:ss) */
+	public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+	/** 时间格式(yyyy-MM-dd) */
+	public final static Pattern p1 = Pattern.compile("\\d{4}(\\-)\\d{2}(\\-)\\d{2}");
+
+	/** 时间格式(yyyy.MM.dd) */
+	public final static Pattern p3 = Pattern.compile("\\d{4}(\\.)\\d{2}(\\.)\\d{2}");
+	/** 时间格式(yyyyMMdd) */
+	public final static Pattern p4 = Pattern.compile("\\d{4}\\d{2}\\d{2}");
+	public final static Pattern p5 = Pattern.compile(
+			"[1-9]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])\\s+(20|21|22|23|[0-1]\\d):[0-5]\\d:[0-5]\\d");
+	/** 时间格式(yyyy/MM/dd) */
+	public final static Pattern p2 = Pattern.compile("\\d{4}(\\/)\\d{2}(\\/)\\d{2}");
+	/** 时间格式(yyyy/MM/d) */
+	public final static Pattern p7 = Pattern.compile("\\d{4}(\\/)\\d{2}(\\/)\\d{1}");
+	/** 时间格式(yyyy/M/dd) */
+	public final static Pattern p6 = Pattern.compile("\\d{4}(\\/)\\d{1}(\\/)\\d{2}");
+	/** 时间格式(yyyy/M/d) */
+	public final static Pattern p8 = Pattern.compile("\\d{4}(\\/)\\d{1}(\\/)\\d{1}");
+
+	/**
+	 * 日期格式化 日期格式为:yyyy-MM-dd
+	 * 
+	 * @param date 日期
+	 * @return 返回yyyy-MM-dd格式日期
+	 */
+	public static String format(Date date) {
+		return format(date, DATE_PATTERN);
+	}
+
+	/**
+	 * 日期格式化 日期格式为:yyyy-MM-dd
+	 * 
+	 * @param date 日期
+	 * @return 返回yyyy-MM-dd格式日期
+	 */
+	public static String formatPattern(Date date, String parrern) {
+		return format(date, parrern);
+	}
+
+	/**
+	 * 日期格式化 日期格式为:yyyy-MM-dd HH:mm:ss
+	 * 
+	 * @param date 日期
+	 * @return 返回yyyy-MM-dd HH:mm:ss格式日期
+	 */
+	public static String formatDatetime(Date date) {
+		return format(date, DATE_TIME_PATTERN);
+	}
+
+	/**
+	 * 得到现在的时间 日期格式化 日期格式为:yyyy-MM-dd HH:mm:ss
+	 * 
+	 * @param date 日期
+	 * @return 返回yyyy-MM-dd HH:mm:ss格式日期
+	 */
+	public static String nowTime() {
+		return format(new Date(), DATE_TIME_PATTERN);
+	}
+
+	/**
+	 * 日期格式化 日期格式为:yyyy-MM-dd
+	 * 
+	 * @param date    日期
+	 * @param pattern 格式,如:DateUtils.DATE_TIME_PATTERN
+	 * @return 返回yyyy-MM-dd格式日期
+	 */
+	public static String format(Date date, String pattern) {
+		if (date != null) {
+			SimpleDateFormat df = new SimpleDateFormat(pattern);
+			return df.format(date);
+		}
+		return null;
+	}
+
+	/**
+	 * 获取linux时间戳(精确到秒)
+	 * 
+	 * @return
+	 */
+	public static String getNowTimeStamp() {
+		long time = System.currentTimeMillis();
+		String nowTimeStamp = String.valueOf(time / 1000);
+		return nowTimeStamp;
+	}
+
+	public static BigDecimal getBetweenYear(Date start, Date end) {
+		long time = end.getTime() - start.getTime();
+		BigDecimal year = new BigDecimal(time / (1000 * 60 * 60 * 24 * 365.0));
+		year = year.setScale(2, BigDecimal.ROUND_HALF_UP);
+		return year;
+	}
+
+	/**
+	 * 日期格式字符串转化为对应的date日期 目前支持格式: yyyy-MM-dd yyyy.MM.dd yyyy/MM/dd yyyyMMdd
+	 * yyyy-MM-dd HH:mm:ss
+	 * 
+	 * @param timeStr
+	 * @return
+	 * @throws ParseException
+	 */
+	public static Date timeStrToDate(String timeStr) {
+
+		try {
+			String format = null;
+			Matcher matcher = p1.matcher(timeStr);
+			if (matcher.find()) {
+				format = DATE_PATTERN;
+			}
+			matcher = p2.matcher(timeStr);
+			if (matcher.find()) {
+				format = DATE_PATTERN2;
+			}
+			matcher = p3.matcher(timeStr);
+			if (matcher.find()) {
+				format = DATE_PATTERN3;
+			}
+			matcher = p4.matcher(timeStr);
+			if (matcher.find()) {
+				format = DATE_PATTERN4;
+			}
+//			matcher = p5.matcher(timeStr);
+//			if (matcher.find()) {
+//				format = DATE_PATTERN5;
+//			}
+			/** 时间格式(yyyy/M/dd) */
+			matcher = p6.matcher(timeStr);
+			if (matcher.find()) {
+				format = DATE_PATTERN6;
+			}
+			matcher = p7.matcher(timeStr);
+			if (matcher.find()) {
+				format = DATE_PATTERN7;
+			}
+			matcher = p8.matcher(timeStr);
+			if (matcher.find()) {
+				format = DATE_PATTERN8;
+			}
+			if (format == null) {
+				return null;
+			}
+			SimpleDateFormat sdf = new SimpleDateFormat(format);
+			return sdf.parse(timeStr);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return new Date();
+		}
+
+	}
+
+	/**
+	 * 获得当天零时零分零秒
+	 * 
+	 * @return
+	 */
+	public static Date initDateByDay() {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(new Date());
+		calendar.set(Calendar.HOUR, 0);
+		calendar.set(Calendar.MINUTE, 0);
+		calendar.set(Calendar.SECOND, 0);
+		return calendar.getTime();
+	}
+
+	/**
+	 * 获取上个季度的时间范围
+	 * 
+	 * @return
+	 */
+	public static Date getLastQuarter() {
+
+		Calendar endCalendar = Calendar.getInstance();
+
+		endCalendar.set(Calendar.MONTH, ((int) endCalendar.get(Calendar.MONTH) / 3 - 1) * 3 + 2);
+		endCalendar.set(Calendar.DAY_OF_MONTH, endCalendar.getActualMinimum(Calendar.DAY_OF_MONTH));
+
+		return endCalendar.getTime();
+	}
+
+	/**
+	 * 根据时间得到季度
+	 * 
+	 * @return
+	 */
+	public static Date getQuarter(Date date) {
+
+		Calendar endCalendar = Calendar.getInstance();
+		endCalendar.setTime(date);
+
+		endCalendar.set(Calendar.MONTH, ((int) endCalendar.get(Calendar.MONTH) / 3 - 1) * 3 + 2);
+		endCalendar.set(Calendar.DAY_OF_MONTH, endCalendar.getActualMinimum(Calendar.DAY_OF_MONTH));
+
+		return endCalendar.getTime();
+	}
+
+	/**
+	 * 得到月份加上季度
+	 * 
+	 * @param date
+	 * @return
+	 */
+	public static String getQuarterByDate(Date date) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		int year = calendar.get(Calendar.YEAR);
+		int month = calendar.get(Calendar.MONTH) + 1;
+		int quarter = 0;
+		if (month >= 1 && month <= 3) {
+			quarter = 1;
+		} else if (month >= 4 && month <= 6) {
+			quarter = 2;
+		} else if (month >= 7 && month <= 9) {
+			quarter = 3;
+		} else if (month >= 10 && month <= 12) {
+			quarter = 4;
+		}
+		return new String(year + "0" + quarter);
+	}
+
+	public static String[] getThreeQuarter() {
+		String[] three = new String[3];
+		Date lastquarter = getLastQuarter();
+		// 最近的第一季度
+		three[0] = getQuarterByDate(lastquarter);
+
+		Date lasttwoquarter = getQuarter(lastquarter);
+		// 最近的第二季度
+		three[1] = getQuarterByDate(lasttwoquarter);
+
+		Date lastThreequarter = getQuarter(lasttwoquarter);
+		// 最近的第三季度
+		three[2] = getQuarterByDate(lastThreequarter);
+		return three;
+	}
+
+	/**
+	 * 统一插入日期格式为yyyy-MM-dd
+	 * 
+	 * @param timeStr
+	 * @return
+	 * @throws ParseException
+	 */
+	public static String format(String timeStr, String formatPattern) {
+		Date d = timeStrToDate(timeStr);
+		return formatPattern(d, formatPattern);
+
+	}
+
+	/**
+	 * 统一插入日期格式为yyyy-MM-dd
+	 * 
+	 * @param timeStr 时间格式
+	 * @return
+	 * @throws ParseException
+	 */
+	public static String defaultFormat(String timeStr) {
+		Date d = timeStrToDate(timeStr);
+		return formatPattern(d, DATE_PATTERN);
+
+	}
+
+	public static String getLastYear() {
+		SimpleDateFormat formats = new SimpleDateFormat("yyyy");
+		Calendar c = Calendar.getInstance();
+		c.add(Calendar.YEAR, -1);
+		Date date = c.getTime();
+		return formats.format(date);
+	}
+
+	public static void main(String[] args) {
+		System.out.println(timeStrToDate("1982/7/23"));
+	}
+}

+ 220 - 0
eladmin-common/src/main/java/me/zhengjie/utils/DateUtil.java

@@ -0,0 +1,220 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.zhengjie.utils;
+
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+ 
+
+/**
+ * @author: liaojinlong
+ * @date: 2020/6/11 16:28
+ * @apiNote: JDK 8  新日期类 格式化与字符串转换 工具类
+ */
+public class DateUtil {
+
+    public static final DateTimeFormatter DFY_MD_HMS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    public static final DateTimeFormatter DFY_MD = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    public static final String YEAR_FORMAT_NUM = "yyyy";
+    public static final String YEAR_FORMAT_WITH_CHINESE_SEPARATOR = "yyyy年";
+    public static final String DATE_FORMAT_WITH_CHINESE_SEPARATOR_AND_NO_ZERO_FILLED = "yyyy年M月d日";
+
+    /**
+     * LocalDateTime 转时间戳
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static Long getTimeStamp(LocalDateTime localDateTime) {
+        return localDateTime.atZone(ZoneId.systemDefault()).toEpochSecond();
+    }
+
+    /**
+     * 时间戳转LocalDateTime
+     *
+     * @param timeStamp /
+     * @return /
+     */
+    public static LocalDateTime fromTimeStamp(Long timeStamp) {
+        return LocalDateTime.ofEpochSecond(timeStamp, 0, OffsetDateTime.now().getOffset());
+    }
+
+    /**
+     * LocalDateTime 转 Date
+     * Jdk8 后 不推荐使用 {@link Date} Date
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static Date toDate(LocalDateTime localDateTime) {
+        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * LocalDate 转 Date
+     * Jdk8 后 不推荐使用 {@link Date} Date
+     *
+     * @param localDate /
+     * @return /
+     */
+    public static Date toDate(LocalDate localDate) {
+        return toDate(localDate.atTime(LocalTime.now(ZoneId.systemDefault())));
+    }
+
+
+    /**
+     * Date转 LocalDateTime
+     * Jdk8 后 不推荐使用 {@link Date} Date
+     *
+     * @param date /
+     * @return /
+     */
+    public static LocalDateTime toLocalDateTime(Date date) {
+        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
+    }
+
+    /**
+     * 日期 格式化
+     *
+     * @param localDateTime /
+     * @param patten /
+     * @return /
+     */
+    public static String localDateTimeFormat(LocalDateTime localDateTime, String patten) {
+        DateTimeFormatter df = DateTimeFormatter.ofPattern(patten);
+        return df.format(localDateTime);
+    }
+
+    /**
+     * 日期 格式化
+     *
+     * @param localDateTime /
+     * @param df /
+     * @return /
+     */
+    public static String localDateTimeFormat(LocalDateTime localDateTime, DateTimeFormatter df) {
+        return df.format(localDateTime);
+    }
+
+    /**
+     * 日期格式化 yyyy-MM-dd HH:mm:ss
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static String localDateTimeFormatyMdHms(LocalDateTime localDateTime) {
+        return DFY_MD_HMS.format(localDateTime);
+    }
+
+    /**
+     * 日期格式化 yyyy-MM-dd
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public String localDateTimeFormatyMd(LocalDateTime localDateTime) {
+        return DFY_MD.format(localDateTime);
+    }
+
+    /**
+     * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, String pattern) {
+        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
+        return LocalDateTime.from(dateTimeFormatter.parse(localDateTime));
+    }
+
+    /**
+     * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, DateTimeFormatter dateTimeFormatter) {
+        return LocalDateTime.from(dateTimeFormatter.parse(localDateTime));
+    }
+
+    /**
+     * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd HH:mm:ss
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static LocalDateTime parseLocalDateTimeFormatyMdHms(String localDateTime) {
+        return LocalDateTime.from(DFY_MD_HMS.parse(localDateTime));
+    }
+
+    /**
+     * 将日期格式化成纯数字表示的年份
+     *
+     * @param date
+     * @return
+     */
+    public static String formatYearToNum(Date date) {
+        SimpleDateFormat destDateFormat = new SimpleDateFormat(YEAR_FORMAT_NUM);
+        return destDateFormat.format(date);
+    }
+
+    /**
+     * 将日期格式化成用中文分割符表示的年份
+     *
+     * @param date
+     * @return
+     */
+    public static String formatYearWithChineseSeparator(Date date) {
+        SimpleDateFormat destDateFormat = new SimpleDateFormat(YEAR_FORMAT_WITH_CHINESE_SEPARATOR);
+        return destDateFormat.format(date);
+    }
+
+    /**
+     * 原格式字符日期转换成目标格式字符日期
+     *
+     * @param date
+     * @param srcFormat
+     * @return
+     * @throws Exception
+     */
+    public static String formatDateWithChineseSeparatorAndNoZeroFilled(String date, String srcFormat) throws Exception {
+        SimpleDateFormat srcDateFormat = new SimpleDateFormat(srcFormat);
+        SimpleDateFormat destDateFormat = new SimpleDateFormat(DATE_FORMAT_WITH_CHINESE_SEPARATOR_AND_NO_ZERO_FILLED);
+        return destDateFormat.format(DateFormatUtils.timeStrToDate(date));
+    }
+
+    /**
+     * 将指定日期转换成目标格式字符串
+     *
+     * @param date
+     * @return
+     */
+    public static String formatDateWithChineseSeparatorAndNoZeroFilled(Date date) {
+        SimpleDateFormat destDateFormat = new SimpleDateFormat(DATE_FORMAT_WITH_CHINESE_SEPARATOR_AND_NO_ZERO_FILLED);
+        return destDateFormat.format(date);
+    }
+
+    public static void main(String[] args) {
+        try {
+            System.out.println(formatDateWithChineseSeparatorAndNoZeroFilled("1989-11-02", "yyyy-MM-dd"));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 47 - 0
eladmin-common/src/main/java/me/zhengjie/utils/ElAdminConstant.java

@@ -0,0 +1,47 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+/**
+ * 常用静态常量
+ *
+ * @author Zheng Jie
+ * @date 2018-12-26
+ */
+public class ElAdminConstant {
+
+    /**
+     * 用于IP定位转换
+     */
+    public static final String REGION = "内网IP|内网IP";
+    /**
+     * win 系统
+     */
+    public static final String WIN = "win";
+
+    /**
+     * mac 系统
+     */
+    public static final String MAC = "mac";
+
+    /**
+     * 常用接口
+     */
+    public static class Url {
+        // IP归属地查询
+        public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true";
+    }
+}

+ 100 - 0
eladmin-common/src/main/java/me/zhengjie/utils/EncryptUtils.java

@@ -0,0 +1,100 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 加密
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+
+public class EncryptUtils {
+
+    private static final String STR_PARAM = "Passw0rd";
+
+    private static Cipher cipher;
+
+    private static final IvParameterSpec IV = new IvParameterSpec(STR_PARAM.getBytes(StandardCharsets.UTF_8));
+
+    private static DESKeySpec getDesKeySpec(String source) throws Exception {
+        if (source == null || source.length() == 0){
+            return null;
+        }
+        cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        String strKey = "Passw0rd";
+        return new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8));
+    }
+
+    /**
+     * 对称加密
+     */
+    public static String desEncrypt(String source) throws Exception {
+        DESKeySpec desKeySpec = getDesKeySpec(source);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV);
+        return byte2hex(
+                cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase();
+    }
+
+    /**
+     * 对称解密
+     */
+    public static String desDecrypt(String source) throws Exception {
+        byte[] src = hex2byte(source.getBytes(StandardCharsets.UTF_8));
+        DESKeySpec desKeySpec = getDesKeySpec(source);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        cipher.init(Cipher.DECRYPT_MODE, secretKey, IV);
+        byte[] retByte = cipher.doFinal(src);
+        return new String(retByte);
+    }
+
+    private static String byte2hex(byte[] inStr) {
+        String stmp;
+        StringBuilder out = new StringBuilder(inStr.length * 2);
+        for (byte b : inStr) {
+            stmp = Integer.toHexString(b & 0xFF);
+            if (stmp.length() == 1) {
+                // 如果是0至F的单位字符串,则添加0
+                out.append("0").append(stmp);
+            } else {
+                out.append(stmp);
+            }
+        }
+        return out.toString();
+    }
+
+    private static byte[] hex2byte(byte[] b) {
+        int size = 2;
+        if ((b.length % size) != 0){
+            throw new IllegalArgumentException("长度不是偶数");
+        }
+        byte[] b2 = new byte[b.length / 2];
+        for (int n = 0; n < b.length; n += size) {
+            String item = new String(b, n, 2);
+            b2[n / 2] = (byte) Integer.parseInt(item, 16);
+        }
+        return b2;
+    }
+}

+ 374 - 0
eladmin-common/src/main/java/me/zhengjie/utils/FileUtil.java

@@ -0,0 +1,374 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.poi.excel.BigExcelWriter;
+import cn.hutool.poi.excel.ExcelUtil;
+import me.zhengjie.exception.BadRequestException;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.xssf.streaming.SXSSFSheet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.security.MessageDigest;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * File工具类,扩展 hutool 工具包
+ *
+ * @author Zheng Jie
+ * @date 2018-12-27
+ */
+public class FileUtil extends cn.hutool.core.io.FileUtil {
+
+    private static final Logger log = LoggerFactory.getLogger(FileUtil.class);
+
+    /**
+     * 系统临时目录
+     * <br>
+     * windows 包含路径分割符,但Linux 不包含,
+     * 在windows \\==\ 前提下,
+     * 为安全起见 同意拼装 路径分割符,
+     * <pre>
+     *       java.io.tmpdir
+     *       windows : C:\Users/xxx\AppData\Local\Temp\
+     *       linux: /temp
+     * </pre>
+     */
+    public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator;
+    /**
+     * 定义GB的计算常量
+     */
+    private static final int GB = 1024 * 1024 * 1024;
+    /**
+     * 定义MB的计算常量
+     */
+    private static final int MB = 1024 * 1024;
+    /**
+     * 定义KB的计算常量
+     */
+    private static final int KB = 1024;
+
+    /**
+     * 格式化小数
+     */
+    private static final DecimalFormat DF = new DecimalFormat("0.00");
+
+    public static final String IMAGE = "图片";
+    public static final String TXT = "文档";
+    public static final String MUSIC = "音乐";
+    public static final String VIDEO = "视频";
+    public static final String OTHER = "其他";
+
+
+    /**
+     * MultipartFile转File
+     */
+    public static File toFile(MultipartFile multipartFile) {
+        // 获取文件名
+        String fileName = multipartFile.getOriginalFilename();
+        // 获取文件后缀
+        String prefix = "." + getExtensionName(fileName);
+        File file = null;
+        try {
+            // 用uuid作为文件名,防止生成的临时文件重复
+            file = new File(SYS_TEM_DIR + IdUtil.simpleUUID() + prefix);
+            // MultipartFile to File
+            multipartFile.transferTo(file);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+        return file;
+    }
+
+    /**
+     * 获取文件扩展名,不带 .
+     */
+    public static String getExtensionName(String filename) {
+        if ((filename != null) && (filename.length() > 0)) {
+            int dot = filename.lastIndexOf('.');
+            if ((dot > -1) && (dot < (filename.length() - 1))) {
+                return filename.substring(dot + 1);
+            }
+        }
+        return filename;
+    }
+
+    /**
+     * Java文件操作 获取不带扩展名的文件名
+     */
+    public static String getFileNameNoEx(String filename) {
+        if ((filename != null) && (filename.length() > 0)) {
+            int dot = filename.lastIndexOf('.');
+            if ((dot > -1) && (dot < (filename.length()))) {
+                return filename.substring(0, dot);
+            }
+        }
+        return filename;
+    }
+
+    /**
+     * 文件大小转换
+     */
+    public static String getSize(long size) {
+        String resultSize;
+        if (size / GB >= 1) {
+            //如果当前Byte的值大于等于1GB
+            resultSize = DF.format(size / (float) GB) + "GB   ";
+        } else if (size / MB >= 1) {
+            //如果当前Byte的值大于等于1MB
+            resultSize = DF.format(size / (float) MB) + "MB   ";
+        } else if (size / KB >= 1) {
+            //如果当前Byte的值大于等于1KB
+            resultSize = DF.format(size / (float) KB) + "KB   ";
+        } else {
+            resultSize = size + "B   ";
+        }
+        return resultSize;
+    }
+
+    /**
+     * inputStream 转 File
+     */
+    static File inputStreamToFile(InputStream ins, String name){
+        File file = new File(SYS_TEM_DIR + name);
+        if (file.exists()) {
+            return file;
+        }
+        OutputStream os = null;
+        try {
+            os = new FileOutputStream(file);
+            int bytesRead;
+            int len = 8192;
+            byte[] buffer = new byte[len];
+            while ((bytesRead = ins.read(buffer, 0, len)) != -1) {
+                os.write(buffer, 0, bytesRead);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            CloseUtil.close(os);
+            CloseUtil.close(ins);
+        }
+        return file;
+    }
+
+    /**
+     * 将文件名解析成文件的上传路径
+     */
+    public static File upload(MultipartFile file, String filePath) {
+        Date date = new Date();
+        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmssS");
+        String name = getFileNameNoEx(file.getOriginalFilename());
+        String suffix = getExtensionName(file.getOriginalFilename());
+        String nowStr = "-" + format.format(date);
+        try {
+            String fileName = name + nowStr + "." + suffix;
+            String path = filePath + fileName;
+            // getCanonicalFile 可解析正确各种路径
+            File dest = new File(path).getCanonicalFile();
+            // 检测是否存在目录
+            if (!dest.getParentFile().exists()) {
+                if (!dest.getParentFile().mkdirs()) {
+                    System.out.println("was not successful.");
+                }
+            }
+            // 文件写入
+            file.transferTo(dest);
+            return dest;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * 导出excel
+     */
+    public static void downloadExcel(List<Map<String, Object>> list, HttpServletResponse response) throws IOException {
+        String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx";
+        File file = new File(tempPath);
+        BigExcelWriter writer = ExcelUtil.getBigWriter(file);
+        // 一次性写出内容,使用默认样式,强制输出标题
+        writer.write(list, true);
+        SXSSFSheet sheet = (SXSSFSheet)writer.getSheet();
+        //上面需要强转SXSSFSheet  不然没有trackAllColumnsForAutoSizing方法
+        sheet.trackAllColumnsForAutoSizing();
+        //列宽自适应
+        writer.autoSizeColumnAll();
+        //response为HttpServletResponse对象
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
+        //test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码
+        response.setHeader("Content-Disposition", "attachment;filename=file.xlsx");
+        ServletOutputStream out = response.getOutputStream();
+        // 终止后删除临时文件
+        file.deleteOnExit();
+        writer.flush(out, true);
+        //此处记得关闭输出Servlet流
+        IoUtil.close(out);
+    }
+
+    public static String getFileType(String type) {
+        String documents = "txt doc pdf ppt pps xlsx xls docx";
+        String music = "mp3 wav wma mpa ram ra aac aif m4a";
+        String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg";
+        String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg";
+        if (image.contains(type)) {
+            return IMAGE;
+        } else if (documents.contains(type)) {
+            return TXT;
+        } else if (music.contains(type)) {
+            return MUSIC;
+        } else if (video.contains(type)) {
+            return VIDEO;
+        } else {
+            return OTHER;
+        }
+    }
+
+    public static void checkSize(long maxSize, long size) {
+        // 1M
+        int len = 1024 * 1024;
+        if (size > (maxSize * len)) {
+            throw new BadRequestException("文件超出规定大小");
+        }
+    }
+
+    /**
+     * 判断两个文件是否相同
+     */
+    public static boolean check(File file1, File file2) {
+        String img1Md5 = getMd5(file1);
+        String img2Md5 = getMd5(file2);
+        if(img1Md5 != null){
+            return img1Md5.equals(img2Md5);
+        }
+        return false;
+    }
+
+    /**
+     * 判断两个文件是否相同
+     */
+    public static boolean check(String file1Md5, String file2Md5) {
+        return file1Md5.equals(file2Md5);
+    }
+
+    private static byte[] getByte(File file) {
+        // 得到文件长度
+        byte[] b = new byte[(int) file.length()];
+        InputStream in = null;
+        try {
+            in = new FileInputStream(file);
+            try {
+                System.out.println(in.read(b));
+            } catch (IOException e) {
+                log.error(e.getMessage(), e);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        } finally {
+            CloseUtil.close(in);
+        }
+        return b;
+    }
+
+    private static String getMd5(byte[] bytes) {
+        // 16进制字符
+        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+        try {
+            MessageDigest mdTemp = MessageDigest.getInstance("MD5");
+            mdTemp.update(bytes);
+            byte[] md = mdTemp.digest();
+            int j = md.length;
+            char[] str = new char[j * 2];
+            int k = 0;
+            // 移位 输出字符串
+            for (byte byte0 : md) {
+                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
+                str[k++] = hexDigits[byte0 & 0xf];
+            }
+            return new String(str);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * 下载文件
+     *
+     * @param request  /
+     * @param response /
+     * @param file     /
+     */
+    public static void downloadFile(HttpServletRequest request, HttpServletResponse response, File file, boolean deleteOnExit) {
+        response.setCharacterEncoding(request.getCharacterEncoding());
+        response.setContentType("application/octet-stream");
+        FileInputStream fis = null;
+        try {
+            fis = new FileInputStream(file);
+            response.setHeader("Content-Disposition", "attachment; filename=" + file.getName());
+            IOUtils.copy(fis, response.getOutputStream());
+            response.flushBuffer();
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                    if (deleteOnExit) {
+                        file.deleteOnExit();
+                    }
+                } catch (IOException e) {
+                    log.error(e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+    public static String getMd5(File file) {
+        return getMd5(getByte(file));
+    }
+
+    /**
+     * 将文件内容转换成Base64编码
+     *
+     * @param path
+     * @return
+     * @throws Exception
+     */
+    public static String encodeBase64File(String path) throws Exception {
+        File file = new File(path);
+        FileInputStream inputFile = new FileInputStream(file);
+        byte[] buffer = new byte[(int) file.length()];
+        inputFile.read(buffer);
+        inputFile.close();
+        return Base64.encodeBase64String(buffer);
+    }
+}

+ 48 - 0
eladmin-common/src/main/java/me/zhengjie/utils/IpAddressUtil.java

@@ -0,0 +1,48 @@
+package me.zhengjie.utils;
+
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.StringUtils;
+
+public class IpAddressUtil {
+	/**
+	 * 获取Ip地址
+	 * 
+	 * @param request
+	 * @return
+	 */
+	public static String getIpAddress(HttpServletRequest request) {
+		String Xip = request.getHeader("X-Real-IP");
+		String XFor = request.getHeader("X-Forwarded-For");
+		if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {
+			// 多次反向代理后会有多个ip值,第一个ip才是真实ip
+			int index = XFor.indexOf(",");
+			if (index != -1) {
+				return XFor.substring(0, index);
+			} else {
+				return XFor;
+			}
+		}
+		XFor = Xip;
+		if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {
+			return XFor;
+		}
+		if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
+			XFor = request.getHeader("Proxy-Client-IP");
+		}
+		if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
+			XFor = request.getHeader("WL-Proxy-Client-IP");
+		}
+		if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
+			XFor = request.getHeader("HTTP_CLIENT_IP");
+		}
+		if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
+			XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
+		}
+		if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
+			XFor = request.getRemoteAddr();
+		}
+		return XFor;
+	}
+}

+ 63 - 0
eladmin-common/src/main/java/me/zhengjie/utils/PageUtil.java

@@ -0,0 +1,63 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import org.springframework.data.domain.Page;
+import java.util.*;
+
+/**
+ * 分页工具
+ * @author Zheng Jie
+ * @date 2018-12-10
+ */
+public class PageUtil extends cn.hutool.core.util.PageUtil {
+
+    /**
+     * List 分页
+     */
+    public static List toPage(int page, int size , List list) {
+        int fromIndex = page * size;
+        int toIndex = page * size + size;
+        if(fromIndex > list.size()){
+            return new ArrayList();
+        } else if(toIndex >= list.size()) {
+            return list.subList(fromIndex,list.size());
+        } else {
+            return list.subList(fromIndex,toIndex);
+        }
+    }
+
+    /**
+     * Page 数据处理,预防redis反序列化报错
+     */
+    public static Map<String,Object> toPage(Page page) {
+        Map<String,Object> map = new LinkedHashMap<>(2);
+        map.put("content",page.getContent());
+        map.put("totalElements",page.getTotalElements());
+        return map;
+    }
+
+    /**
+     * 自定义分页
+     */
+    public static Map<String,Object> toPage(Object object, Object totalElements) {
+        Map<String,Object> map = new LinkedHashMap<>(2);
+        map.put("content",object);
+        map.put("totalElements",totalElements);
+        return map;
+    }
+
+}

+ 208 - 0
eladmin-common/src/main/java/me/zhengjie/utils/QueryHelp.java

@@ -0,0 +1,208 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.extern.slf4j.Slf4j;
+import me.zhengjie.annotation.DataPermission;
+import me.zhengjie.annotation.Query;
+import javax.persistence.criteria.*;
+import java.lang.reflect.Field;
+import java.util.*;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-6-4 14:59:48
+ */
+@Slf4j
+@SuppressWarnings({"unchecked","all"})
+public class QueryHelp {
+
+    public static <R, Q> Predicate getPredicate(Root<R> root, Q query, CriteriaBuilder cb) {
+        List<Predicate> list = new ArrayList<>();
+        if(query == null){
+            return cb.and(list.toArray(new Predicate[0]));
+        }
+        // 数据权限验证
+        DataPermission permission = query.getClass().getAnnotation(DataPermission.class);
+        if(permission != null){
+            // 获取数据权限
+            List<Long> dataScopes = SecurityUtils.getCurrentUserDataScope();
+            if(CollectionUtil.isNotEmpty(dataScopes)){
+                if(StringUtils.isNotBlank(permission.joinName()) && StringUtils.isNotBlank(permission.fieldName())) {
+                    Join join = root.join(permission.joinName(), JoinType.LEFT);
+                    list.add(getExpression(permission.fieldName(),join, root).in(dataScopes));
+                } else if (StringUtils.isBlank(permission.joinName()) && StringUtils.isNotBlank(permission.fieldName())) {
+                    list.add(getExpression(permission.fieldName(),null, root).in(dataScopes));
+                }
+            }
+        }
+        try {
+            List<Field> fields = getAllFields(query.getClass(), new ArrayList<>());
+            for (Field field : fields) {
+                boolean accessible = field.isAccessible();
+                // 设置对象的访问权限,保证对private的属性的访
+                field.setAccessible(true);
+                Query q = field.getAnnotation(Query.class);
+                if (q != null) {
+                    String propName = q.propName();
+                    String joinName = q.joinName();
+                    String blurry = q.blurry();
+                    String attributeName = isBlank(propName) ? field.getName() : propName;
+                    Class<?> fieldType = field.getType();
+                    Object val = field.get(query);
+                    if (ObjectUtil.isNull(val) || "".equals(val)) {
+                        continue;
+                    }
+                    Join join = null;
+                    // 模糊多字段
+                    if (ObjectUtil.isNotEmpty(blurry)) {
+                        String[] blurrys = blurry.split(",");
+                        List<Predicate> orPredicate = new ArrayList<>();
+                        for (String s : blurrys) {
+                            orPredicate.add(cb.like(root.get(s)
+                                    .as(String.class), "%" + val.toString() + "%"));
+                        }
+                        Predicate[] p = new Predicate[orPredicate.size()];
+                        list.add(cb.or(orPredicate.toArray(p)));
+                        continue;
+                    }
+                    if (ObjectUtil.isNotEmpty(joinName)) {
+                        String[] joinNames = joinName.split(">");
+                        for (String name : joinNames) {
+                            switch (q.join()) {
+                                case LEFT:
+                                    if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){
+                                        join = join.join(name, JoinType.LEFT);
+                                    } else {
+                                        join = root.join(name, JoinType.LEFT);
+                                    }
+                                    break;
+                                case RIGHT:
+                                    if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){
+                                        join = join.join(name, JoinType.RIGHT);
+                                    } else {
+                                        join = root.join(name, JoinType.RIGHT);
+                                    }
+                                    break;
+                                case INNER:
+                                    if(ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(val)){
+                                        join = join.join(name, JoinType.INNER);
+                                    } else {
+                                        join = root.join(name, JoinType.INNER);
+                                    }
+                                    break;
+                                default: break;
+                            }
+                        }
+                    }
+                    switch (q.type()) {
+                        case EQUAL:
+                            list.add(cb.equal(getExpression(attributeName,join,root)
+                                    .as((Class<? extends Comparable>) fieldType),val));
+                            break;
+                        case GREATER_THAN:
+                            list.add(cb.greaterThanOrEqualTo(getExpression(attributeName,join,root)
+                                    .as((Class<? extends Comparable>) fieldType), (Comparable) val));
+                            break;
+                        case LESS_THAN:
+                            list.add(cb.lessThanOrEqualTo(getExpression(attributeName,join,root)
+                                    .as((Class<? extends Comparable>) fieldType), (Comparable) val));
+                            break;
+                        case LESS_THAN_NQ:
+                            list.add(cb.lessThan(getExpression(attributeName,join,root)
+                                    .as((Class<? extends Comparable>) fieldType), (Comparable) val));
+                            break;
+                        case INNER_LIKE:
+                            list.add(cb.like(getExpression(attributeName,join,root)
+                                    .as(String.class), "%" + val.toString() + "%"));
+                            break;
+                        case LEFT_LIKE:
+                            list.add(cb.like(getExpression(attributeName,join,root)
+                                    .as(String.class), "%" + val.toString()));
+                            break;
+                        case RIGHT_LIKE:
+                            list.add(cb.like(getExpression(attributeName,join,root)
+                                    .as(String.class), val.toString() + "%"));
+                            break;
+                        case IN:
+                            if (CollUtil.isNotEmpty((Collection<Object>)val)) {
+                                list.add(getExpression(attributeName,join,root).in((Collection<Object>) val));
+                            }
+                            break;
+                        case NOT_IN:
+                            if (CollUtil.isNotEmpty((Collection<Object>)val)) {
+                                list.add(getExpression(attributeName,join,root).in((Collection<Object>) val).not());
+                            }
+                            break;
+                        case NOT_EQUAL:
+                            list.add(cb.notEqual(getExpression(attributeName,join,root), val));
+                            break;
+                        case NOT_NULL:
+                            list.add(cb.isNotNull(getExpression(attributeName,join,root)));
+                            break;
+                        case IS_NULL:
+                            list.add(cb.isNull(getExpression(attributeName,join,root)));
+                            break;
+                        case BETWEEN:
+                            List<Object> between = new ArrayList<>((List<Object>)val);
+                            list.add(cb.between(getExpression(attributeName, join, root).as((Class<? extends Comparable>) between.get(0).getClass()),
+                                    (Comparable) between.get(0), (Comparable) between.get(1)));
+                            break;
+                        default: break;
+                    }
+                }
+                field.setAccessible(accessible);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        int size = list.size();
+        return cb.and(list.toArray(new Predicate[size]));
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T, R> Expression<T> getExpression(String attributeName, Join join, Root<R> root) {
+        if (ObjectUtil.isNotEmpty(join)) {
+            return join.get(attributeName);
+        } else {
+            return root.get(attributeName);
+        }
+    }
+
+    private static boolean isBlank(final CharSequence cs) {
+        int strLen;
+        if (cs == null || (strLen = cs.length()) == 0) {
+            return true;
+        }
+        for (int i = 0; i < strLen; i++) {
+            if (!Character.isWhitespace(cs.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static List<Field> getAllFields(Class clazz, List<Field> fields) {
+        if (clazz != null) {
+            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
+            getAllFields(clazz.getSuperclass(), fields);
+        }
+        return fields;
+    }
+}

+ 714 - 0
eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java

@@ -0,0 +1,714 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.*;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author /
+ */
+@Component
+@SuppressWarnings({"unchecked", "all"})
+public class RedisUtils {
+    private static final Logger log = LoggerFactory.getLogger(RedisUtils.class);
+    private RedisTemplate<Object, Object> redisTemplate;
+    @Value("${jwt.online-key}")
+    private String onlineKey;
+
+    public RedisUtils(RedisTemplate<Object, Object> redisTemplate) {
+        this.redisTemplate = redisTemplate;
+    }
+
+    /**
+     * 指定缓存失效时间
+     *
+     * @param key  键
+     * @param time 时间(秒)
+     */
+    public boolean expire(String key, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.expire(key, time, TimeUnit.SECONDS);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 指定缓存失效时间
+     *
+     * @param key      键
+     * @param time     时间(秒)
+     * @param timeUnit 单位
+     */
+    public boolean expire(String key, long time, TimeUnit timeUnit) {
+        try {
+            if (time > 0) {
+                redisTemplate.expire(key, time, timeUnit);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 根据 key 获取过期时间
+     *
+     * @param key 键 不能为null
+     * @return 时间(秒) 返回0代表为永久有效
+     */
+    public long getExpire(Object key) {
+        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 查找匹配key
+     *
+     * @param pattern key
+     * @return /
+     */
+    public List<String> scan(String pattern) {
+        ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
+        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
+        RedisConnection rc = Objects.requireNonNull(factory).getConnection();
+        Cursor<byte[]> cursor = rc.scan(options);
+        List<String> result = new ArrayList<>();
+        while (cursor.hasNext()) {
+            result.add(new String(cursor.next()));
+        }
+        try {
+            RedisConnectionUtils.releaseConnection(rc, factory);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return result;
+    }
+
+    /**
+     * 分页查询 key
+     *
+     * @param patternKey key
+     * @param page       页码
+     * @param size       每页数目
+     * @return /
+     */
+    public List<String> findKeysForPage(String patternKey, int page, int size) {
+        ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
+        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
+        RedisConnection rc = Objects.requireNonNull(factory).getConnection();
+        Cursor<byte[]> cursor = rc.scan(options);
+        List<String> result = new ArrayList<>(size);
+        int tmpIndex = 0;
+        int fromIndex = page * size;
+        int toIndex = page * size + size;
+        while (cursor.hasNext()) {
+            if (tmpIndex >= fromIndex && tmpIndex < toIndex) {
+                result.add(new String(cursor.next()));
+                tmpIndex++;
+                continue;
+            }
+            // 获取到满足条件的数据后,就可以退出了
+            if (tmpIndex >= toIndex) {
+                break;
+            }
+            tmpIndex++;
+            cursor.next();
+        }
+        try {
+            RedisConnectionUtils.releaseConnection(rc, factory);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return result;
+    }
+
+    /**
+     * 判断key是否存在
+     *
+     * @param key 键
+     * @return true 存在 false不存在
+     */
+    public boolean hasKey(String key) {
+        try {
+            return redisTemplate.hasKey(key);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 删除缓存
+     *
+     * @param key 可以传一个值 或多个
+     */
+    public void del(String... keys) {
+        if (keys != null && keys.length > 0) {
+            if (keys.length == 1) {
+                boolean result = redisTemplate.delete(keys[0]);
+                log.debug("--------------------------------------------");
+                log.debug(new StringBuilder("删除缓存:").append(keys[0]).append(",结果:").append(result).toString());
+                log.debug("--------------------------------------------");
+            } else {
+                Set<Object> keySet = new HashSet<>();
+                for (String key : keys) {
+                    keySet.addAll(redisTemplate.keys(key));
+                }
+                long count = redisTemplate.delete(keySet);
+                log.debug("--------------------------------------------");
+                log.debug("成功删除缓存:" + keySet.toString());
+                log.debug("缓存删除数量:" + count + "个");
+                log.debug("--------------------------------------------");
+            }
+        }
+    }
+
+    // ============================String=============================
+
+    /**
+     * 普通缓存获取
+     *
+     * @param key 键
+     * @return 值
+     */
+    public Object get(String key) {
+        return key == null ? null : redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 批量获取
+     *
+     * @param keys
+     * @return
+     */
+    public List<Object> multiGet(List<String> keys) {
+        List list = redisTemplate.opsForValue().multiGet(Sets.newHashSet(keys));
+        List resultList = Lists.newArrayList();
+        Optional.ofNullable(list).ifPresent(e-> list.forEach(ele-> Optional.ofNullable(ele).ifPresent(resultList::add)));
+        return resultList;
+    }
+
+    /**
+     * 普通缓存放入
+     *
+     * @param key   键
+     * @param value 值
+     * @return true成功 false失败
+     */
+    public boolean set(String key, Object value) {
+        try {
+            redisTemplate.opsForValue().set(key, value);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 普通缓存放入并设置时间
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
+     * @return true成功 false 失败
+     */
+    public boolean set(String key, Object value, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+            } else {
+                set(key, value);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 普通缓存放入并设置时间
+     *
+     * @param key      键
+     * @param value    值
+     * @param time     时间
+     * @param timeUnit 类型
+     * @return true成功 false 失败
+     */
+    public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
+        try {
+            if (time > 0) {
+                redisTemplate.opsForValue().set(key, value, time, timeUnit);
+            } else {
+                set(key, value);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    // ================================Map=================================
+
+    /**
+     * HashGet
+     *
+     * @param key  键 不能为null
+     * @param item 项 不能为null
+     * @return 值
+     */
+    public Object hget(String key, String item) {
+        return redisTemplate.opsForHash().get(key, item);
+    }
+
+    /**
+     * 获取hashKey对应的所有键值
+     *
+     * @param key 键
+     * @return 对应的多个键值
+     */
+    public Map<Object, Object> hmget(String key) {
+        return redisTemplate.opsForHash().entries(key);
+
+    }
+
+    /**
+     * HashSet
+     *
+     * @param key 键
+     * @param map 对应多个键值
+     * @return true 成功 false 失败
+     */
+    public boolean hmset(String key, Map<String, Object> map) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * HashSet 并设置时间
+     *
+     * @param key  键
+     * @param map  对应多个键值
+     * @param time 时间(秒)
+     * @return true成功 false失败
+     */
+    public boolean hmset(String key, Map<String, Object> map, long time) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     *
+     * @param key   键
+     * @param item  项
+     * @param value 值
+     * @return true 成功 false失败
+     */
+    public boolean hset(String key, String item, Object value) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     *
+     * @param key   键
+     * @param item  项
+     * @param value 值
+     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
+     * @return true 成功 false失败
+     */
+    public boolean hset(String key, String item, Object value, long time) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 删除hash表中的值
+     *
+     * @param key  键 不能为null
+     * @param item 项 可以使多个 不能为null
+     */
+    public void hdel(String key, Object... item) {
+        redisTemplate.opsForHash().delete(key, item);
+    }
+
+    /**
+     * 判断hash表中是否有该项的值
+     *
+     * @param key  键 不能为null
+     * @param item 项 不能为null
+     * @return true 存在 false不存在
+     */
+    public boolean hHasKey(String key, String item) {
+        return redisTemplate.opsForHash().hasKey(key, item);
+    }
+
+    /**
+     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
+     *
+     * @param key  键
+     * @param item 项
+     * @param by   要增加几(大于0)
+     * @return
+     */
+    public double hincr(String key, String item, double by) {
+        return redisTemplate.opsForHash().increment(key, item, by);
+    }
+
+    /**
+     * hash递减
+     *
+     * @param key  键
+     * @param item 项
+     * @param by   要减少记(小于0)
+     * @return
+     */
+    public double hdecr(String key, String item, double by) {
+        return redisTemplate.opsForHash().increment(key, item, -by);
+    }
+
+    // ============================set=============================
+
+    /**
+     * 根据key获取Set中的所有值
+     *
+     * @param key 键
+     * @return
+     */
+    public Set<Object> sGet(String key) {
+        try {
+            return redisTemplate.opsForSet().members(key);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 根据value从一个set中查询,是否存在
+     *
+     * @param key   键
+     * @param value 值
+     * @return true 存在 false不存在
+     */
+    public boolean sHasKey(String key, Object value) {
+        try {
+            return redisTemplate.opsForSet().isMember(key, value);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 将数据放入set缓存
+     *
+     * @param key    键
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public long sSet(String key, Object... values) {
+        try {
+            return redisTemplate.opsForSet().add(key, values);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return 0;
+        }
+    }
+
+    /**
+     * 将set数据放入缓存
+     *
+     * @param key    键
+     * @param time   时间(秒)
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public long sSetAndTime(String key, long time, Object... values) {
+        try {
+            Long count = redisTemplate.opsForSet().add(key, values);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return count;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return 0;
+        }
+    }
+
+    /**
+     * 获取set缓存的长度
+     *
+     * @param key 键
+     * @return
+     */
+    public long sGetSetSize(String key) {
+        try {
+            return redisTemplate.opsForSet().size(key);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return 0;
+        }
+    }
+
+    /**
+     * 移除值为value的
+     *
+     * @param key    键
+     * @param values 值 可以是多个
+     * @return 移除的个数
+     */
+    public long setRemove(String key, Object... values) {
+        try {
+            Long count = redisTemplate.opsForSet().remove(key, values);
+            return count;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return 0;
+        }
+    }
+
+    // ===============================list=================================
+
+    /**
+     * 获取list缓存的内容
+     *
+     * @param key   键
+     * @param start 开始
+     * @param end   结束 0 到 -1代表所有值
+     * @return
+     */
+    public List<Object> lGet(String key, long start, long end) {
+        try {
+            return redisTemplate.opsForList().range(key, start, end);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 获取list缓存的长度
+     *
+     * @param key 键
+     * @return
+     */
+    public long lGetListSize(String key) {
+        try {
+            return redisTemplate.opsForList().size(key);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return 0;
+        }
+    }
+
+    /**
+     * 通过索引 获取list中的值
+     *
+     * @param key   键
+     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
+     * @return
+     */
+    public Object lGetIndex(String key, long index) {
+        try {
+            return redisTemplate.opsForList().index(key, index);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @return
+     */
+    public boolean lSet(String key, Object value) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒)
+     * @return
+     */
+    public boolean lSet(String key, Object value, long time) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @return
+     */
+    public boolean lSet(String key, List<Object> value) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒)
+     * @return
+     */
+    public boolean lSet(String key, List<Object> value, long time) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 根据索引修改list中的某条数据
+     *
+     * @param key   键
+     * @param index 索引
+     * @param value 值
+     * @return /
+     */
+    public boolean lUpdateIndex(String key, long index, Object value) {
+        try {
+            redisTemplate.opsForList().set(key, index, value);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 移除N个值为value
+     *
+     * @param key   键
+     * @param count 移除多少个
+     * @param value 值
+     * @return 移除的个数
+     */
+    public long lRemove(String key, long count, Object value) {
+        try {
+            return redisTemplate.opsForList().remove(key, count, value);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return 0;
+        }
+    }
+
+    /**
+     * @param prefix 前缀
+     * @param ids    id
+     */
+    public void delByKeys(String prefix, Set<Long> ids) {
+        Set<Object> keys = new HashSet<>();
+        for (Long id : ids) {
+            keys.addAll(redisTemplate.keys(new StringBuffer(prefix).append(id).toString()));
+        }
+        long count = redisTemplate.delete(keys);
+        // 此处提示可自行删除
+        log.debug("--------------------------------------------");
+        log.debug("成功删除缓存:" + keys.toString());
+        log.debug("缓存删除数量:" + count + "个");
+        log.debug("--------------------------------------------");
+    }
+
+    public void increment(String key) {
+        ValueOperations<Object, Object> objectObjectValueOperations = redisTemplate.opsForValue();
+        objectObjectValueOperations.increment(key);
+        expire(key,30,TimeUnit.MINUTES);
+    }
+}

+ 33 - 0
eladmin-common/src/main/java/me/zhengjie/utils/RequestHolder.java

@@ -0,0 +1,33 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Objects;
+
+/**
+ * 获取 HttpServletRequest
+ * @author Zheng Jie
+ * @date 2018-11-24
+ */
+public class RequestHolder {
+
+    public static HttpServletRequest getHttpServletRequest() {
+        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
+    }
+}

+ 198 - 0
eladmin-common/src/main/java/me/zhengjie/utils/RsaUtils.java

@@ -0,0 +1,198 @@
+package me.zhengjie.utils;
+
+import org.apache.commons.codec.binary.Base64;
+import javax.crypto.Cipher;
+import java.io.ByteArrayOutputStream;
+import java.security.*;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * @author https://www.cnblogs.com/nihaorz/p/10690643.html
+ * @description Rsa 工具类,公钥私钥生成,加解密
+ * @date 2020-05-18
+ **/
+public class RsaUtils {
+
+    private static final String SRC = "123456";
+
+    public static void main(String[] args) throws Exception {
+        System.out.println("\n");
+        RsaKeyPair keyPair = generateKeyPair();
+        System.out.println("公钥:" + keyPair.getPublicKey());
+        System.out.println("私钥:" + keyPair.getPrivateKey());
+        System.out.println("\n");
+        test1(keyPair);
+        System.out.println("\n");
+        test2(keyPair);
+        System.out.println("\n");
+    }
+
+    /**
+     * 公钥加密私钥解密
+     */
+    private static void test1(RsaKeyPair keyPair) throws Exception {
+        System.out.println("***************** 公钥加密私钥解密开始 *****************");
+        String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC);
+        String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);
+        System.out.println("加密前:" + RsaUtils.SRC);
+        System.out.println("加密后:" + text1);
+        System.out.println("解密后:" + text2);
+        if (RsaUtils.SRC.equals(text2)) {
+            System.out.println("解密字符串和原始字符串一致,解密成功");
+        } else {
+            System.out.println("解密字符串和原始字符串不一致,解密失败");
+        }
+        System.out.println("***************** 公钥加密私钥解密结束 *****************");
+    }
+
+    /**
+     * 私钥加密公钥解密
+     * @throws Exception /
+     */
+    private static void test2(RsaKeyPair keyPair) throws Exception {
+        System.out.println("***************** 私钥加密公钥解密开始 *****************");
+        String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC);
+        String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1);
+        System.out.println("加密前:" + RsaUtils.SRC);
+        System.out.println("加密后:" + text1);
+        System.out.println("解密后:" + text2);
+        if (RsaUtils.SRC.equals(text2)) {
+            System.out.println("解密字符串和原始字符串一致,解密成功");
+        } else {
+            System.out.println("解密字符串和原始字符串不一致,解密失败");
+        }
+        System.out.println("***************** 私钥加密公钥解密结束 *****************");
+    }
+
+    /**
+     * 公钥解密
+     *
+     * @param publicKeyText 公钥
+     * @param text 待解密的信息
+     * @return /
+     * @throws Exception /
+     */
+    public static String decryptByPublicKey(String publicKeyText, String text) throws Exception {
+        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.DECRYPT_MODE, publicKey);
+        byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text));
+        return new String(result);
+    }
+
+    /**
+     * 私钥加密
+     *
+     * @param privateKeyText 私钥
+     * @param text 待加密的信息
+     * @return /
+     * @throws Exception /
+     */
+    public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception {
+        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
+        byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes());
+        return Base64.encodeBase64String(result);
+    }
+
+    /**
+     * 私钥解密
+     *
+     * @param privateKeyText 私钥
+     * @param text 待解密的文本
+     * @return /
+     * @throws Exception /
+     */
+    public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {
+        PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.DECRYPT_MODE, privateKey);
+        byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text));
+        return new String(result);
+    }
+
+    /**
+     * 公钥加密
+     *
+     * @param publicKeyText 公钥
+     * @param text 待加密的文本
+     * @return /
+     */
+    public static String encryptByPublicKey(String publicKeyText, String text) throws Exception {
+        X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+        byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes());
+        return Base64.encodeBase64String(result);
+    }
+
+    private static byte[] doLongerCipherFinal(int opMode,Cipher cipher, byte[] source) throws Exception {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        if (opMode == Cipher.DECRYPT_MODE) {
+            out.write(cipher.doFinal(source));
+        } else {
+            int offset = 0;
+            int totalSize = source.length;
+            while (totalSize - offset > 0) {
+                int size = Math.min(cipher.getOutputSize(0) - 11, totalSize - offset);
+                out.write(cipher.doFinal(source, offset, size));
+                offset += size;
+            }
+        }
+        out.close();
+        return out.toByteArray();
+    }
+
+    /**
+     * 构建RSA密钥对
+     *
+     * @return /
+     * @throws NoSuchAlgorithmException /
+     */
+    public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+        keyPairGenerator.initialize(1024);
+        KeyPair keyPair = keyPairGenerator.generateKeyPair();
+        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+        String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
+        String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
+        return new RsaKeyPair(publicKeyString, privateKeyString);
+    }
+
+
+    /**
+     * RSA密钥对对象
+     */
+    public static class RsaKeyPair {
+
+        private final String publicKey;
+        private final String privateKey;
+
+        public RsaKeyPair(String publicKey, String privateKey) {
+            this.publicKey = publicKey;
+            this.privateKey = privateKey;
+        }
+
+        public String getPublicKey() {
+            return publicKey;
+        }
+
+        public String getPrivateKey() {
+            return privateKey;
+        }
+
+    }
+}

+ 73 - 0
eladmin-common/src/main/java/me/zhengjie/utils/SecurityUtils.java

@@ -0,0 +1,73 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import lombok.extern.slf4j.Slf4j;
+import me.zhengjie.exception.BadRequestException;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.List;
+
+/**
+ * 获取当前登录的用户
+ * @author Zheng Jie
+ * @date 2019-01-17
+ */
+@Slf4j
+public class SecurityUtils {
+
+    /**
+     * 获取当前登录的用户
+     * @return UserDetails
+     */
+    public static UserDetails getCurrentUser() {
+        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication == null) {
+            throw new BadRequestException(HttpStatus.UNAUTHORIZED, "当前登录状态过期");
+        }
+        if (authentication.getPrincipal() instanceof UserDetails) {
+            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
+            return userDetails;
+        }
+        throw new BadRequestException(HttpStatus.UNAUTHORIZED, "找不到当前登录的信息");
+    }
+
+    /**
+     * 获取系统用户名称
+     *
+     * @return 系统用户名称
+     */
+    public static String getCurrentUsername() {
+        UserDetails userDetails = getCurrentUser();
+        return userDetails.getUsername();
+    }
+
+    /**
+     * 获取当前用户的数据权限
+     * @return /
+     */
+    public static List<Long> getCurrentUserDataScope(){
+        UserDetails userDetails = getCurrentUser();
+        JSONArray array = JSONUtil.parseArray(new JSONObject(userDetails).get("dataScopes"));
+        return JSONUtil.toList(array,Long.class);
+    }
+}

+ 145 - 0
eladmin-common/src/main/java/me/zhengjie/utils/SpringContextHolder.java

@@ -0,0 +1,145 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.env.Environment;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Jie
+ * @date 2019-01-07
+ */
+@Slf4j
+public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
+
+    private static ApplicationContext applicationContext = null;
+    private static final List<CallBack> CALL_BACKS = new ArrayList<>();
+    private static boolean addCallback = true;
+
+    /**
+     * 针对 某些初始化方法,在SpringContextHolder 未初始化时 提交回调方法。
+     * 在SpringContextHolder 初始化后,进行回调使用
+     *
+     * @param callBack 回调函数
+     */
+    public synchronized static void addCallBacks(CallBack callBack) {
+        if (addCallback) {
+            SpringContextHolder.CALL_BACKS.add(callBack);
+        } else {
+            log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName());
+            callBack.executor();
+        }
+    }
+
+    /**
+     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getBean(String name) {
+        assertContextInjected();
+        return (T) applicationContext.getBean(name);
+    }
+
+    /**
+     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+     */
+    public static <T> T getBean(Class<T> requiredType) {
+        assertContextInjected();
+        return applicationContext.getBean(requiredType);
+    }
+
+    /**
+     * 获取SpringBoot 配置信息
+     *
+     * @param property     属性key
+     * @param defaultValue 默认值
+     * @param requiredType 返回类型
+     * @return /
+     */
+    public static <T> T getProperties(String property, T defaultValue, Class<T> requiredType) {
+        T result = defaultValue;
+        try {
+            result = getBean(Environment.class).getProperty(property, requiredType);
+        } catch (Exception ignored) {}
+        return result;
+    }
+
+    /**
+     * 获取SpringBoot 配置信息
+     *
+     * @param property 属性key
+     * @return /
+     */
+    public static String getProperties(String property) {
+        return getProperties(property, null, String.class);
+    }
+
+    /**
+     * 获取SpringBoot 配置信息
+     *
+     * @param property     属性key
+     * @param requiredType 返回类型
+     * @return /
+     */
+    public static <T> T getProperties(String property, Class<T> requiredType) {
+        return getProperties(property, null, requiredType);
+    }
+
+    /**
+     * 检查ApplicationContext不为空.
+     */
+    private static void assertContextInjected() {
+        if (applicationContext == null) {
+            throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" +
+                    ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");
+        }
+    }
+
+    /**
+     * 清除SpringContextHolder中的ApplicationContext为Null.
+     */
+    private static void clearHolder() {
+        log.debug("清除SpringContextHolder中的ApplicationContext:"
+                + applicationContext);
+        applicationContext = null;
+    }
+
+    @Override
+    public void destroy() {
+        SpringContextHolder.clearHolder();
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        if (SpringContextHolder.applicationContext != null) {
+            log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);
+        }
+        SpringContextHolder.applicationContext = applicationContext;
+        if (addCallback) {
+            for (CallBack callBack : SpringContextHolder.CALL_BACKS) {
+                callBack.executor();
+            }
+            CALL_BACKS.clear();
+        }
+        SpringContextHolder.addCallback = false;
+    }
+}

+ 289 - 0
eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java

@@ -0,0 +1,289 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import lombok.extern.slf4j.Slf4j;
+import me.zhengjie.config.ElAdminProperties;
+import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
+import net.dreamlu.mica.ip2region.core.IpInfo;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.UnknownHostException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Enumeration;
+
+/**
+ * @author Zheng Jie 字符串工具类, 继承org.apache.commons.lang3.StringUtils类
+ */
+@Slf4j
+public class StringUtils extends org.apache.commons.lang3.StringUtils {
+
+	private static final char SEPARATOR = '_';
+	private static final String UNKNOWN = "unknown";
+
+	/**
+	 * 注入bean
+	 */
+	private final static Ip2regionSearcher IP_SEARCHER = SpringContextHolder.getBean(Ip2regionSearcher.class);
+
+//	private static final UserAgentAnalyzer USER_AGENT_ANALYZER = UserAgentAnalyzer.newBuilder().hideMatcherLoadStats()
+//			.withCache(10000).withField(UserAgent.AGENT_NAME_VERSION).build();
+
+	/**
+	 * 驼峰命名法工具
+	 *
+	 * @return toCamelCase(" hello_world ") == "helloWorld"
+	 *         toCapitalizeCamelCase("hello_world") == "HelloWorld"
+	 *         toUnderScoreCase("helloWorld") = "hello_world"
+	 */
+	public static String toCamelCase(String s) {
+		if (s == null) {
+			return null;
+		}
+
+		s = s.toLowerCase();
+
+		StringBuilder sb = new StringBuilder(s.length());
+		boolean upperCase = false;
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+
+			if (c == SEPARATOR) {
+				upperCase = true;
+			} else if (upperCase) {
+				sb.append(Character.toUpperCase(c));
+				upperCase = false;
+			} else {
+				sb.append(c);
+			}
+		}
+
+		return sb.toString();
+	}
+
+	/**
+	 * 驼峰命名法工具
+	 *
+	 * @return toCamelCase(" hello_world ") == "helloWorld"
+	 *         toCapitalizeCamelCase("hello_world") == "HelloWorld"
+	 *         toUnderScoreCase("helloWorld") = "hello_world"
+	 */
+	public static String toCapitalizeCamelCase(String s) {
+		if (s == null) {
+			return null;
+		}
+		s = toCamelCase(s);
+		return s.substring(0, 1).toUpperCase() + s.substring(1);
+	}
+
+	/**
+	 * 驼峰命名法工具
+	 *
+	 * @return toCamelCase(" hello_world ") == "helloWorld"
+	 *         toCapitalizeCamelCase("hello_world") == "HelloWorld"
+	 *         toUnderScoreCase("helloWorld") = "hello_world"
+	 */
+	static String toUnderScoreCase(String s) {
+		if (s == null) {
+			return null;
+		}
+
+		StringBuilder sb = new StringBuilder();
+		boolean upperCase = false;
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+
+			boolean nextUpperCase = true;
+
+			if (i < (s.length() - 1)) {
+				nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
+			}
+
+			if ((i > 0) && Character.isUpperCase(c)) {
+				if (!upperCase || !nextUpperCase) {
+					sb.append(SEPARATOR);
+				}
+				upperCase = true;
+			} else {
+				upperCase = false;
+			}
+
+			sb.append(Character.toLowerCase(c));
+		}
+
+		return sb.toString();
+	}
+
+	/**
+	 * 获取ip地址
+	 */
+	public static String getIp(HttpServletRequest request) {
+		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("WL-Proxy-Client-IP");
+		}
+		if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+			ip = request.getRemoteAddr();
+		}
+		String comma = ",";
+		String localhost = "127.0.0.1";
+		if (ip.contains(comma)) {
+			ip = ip.split(",")[0];
+		}
+		if (localhost.equals(ip)) {
+			// 获取本机真正的ip地址
+			try {
+				ip = InetAddress.getLocalHost().getHostAddress();
+			} catch (UnknownHostException e) {
+				log.error(e.getMessage(), e);
+			}
+		}
+		return ip;
+	}
+
+	/**
+	 * 根据ip获取详细地址
+	 */
+	public static String getCityInfo(String ip) {
+		if (ElAdminProperties.ipLocal) {
+			return getLocalCityInfo(ip);
+		} else {
+			return getHttpCityInfo(ip);
+		}
+	}
+
+	/**
+	 * 根据ip获取详细地址
+	 */
+	public static String getHttpCityInfo(String ip) {
+		String api = String.format(ElAdminConstant.Url.IP_URL, ip);
+		JSONObject object = JSONUtil.parseObj(HttpUtil.get(api));
+		return object.get("addr", String.class);
+	}
+
+	/**
+	 * 根据ip获取详细地址
+	 */
+	public static String getLocalCityInfo(String ip) {
+		IpInfo ipInfo = IP_SEARCHER.memorySearch(ip);
+		if (ipInfo != null) {
+			return ipInfo.getAddress();
+		}
+		return null;
+
+	}
+
+	public static String getBrowser(HttpServletRequest request) {
+		String userAgent = request.getHeader("User-Agent");
+		// 以空格来处理
+		try {
+			String[] browser = userAgent.split(" ");
+			for (String agent : browser) {
+				if (agent.indexOf("msie 7") > 0) {
+					return agent;
+				} else if (agent.indexOf("msie 8") != -1) {
+					return agent;
+				} else if (agent.indexOf("msie 9") != -1) {
+					return agent;
+				} else if (agent.indexOf("msie 10") != -1) {
+					return agent;
+				} else if (agent.indexOf("msie") != -1) {
+					return agent;
+				} else if (agent.indexOf("Chrome") != -1) {
+					return agent;
+				} else if (agent.indexOf("opera") != -1) {
+					return agent;
+				} else if (agent.indexOf("firefox") != -1) {
+					return agent;
+				} else if (agent.indexOf("webkit") != -1) {
+					return agent;
+				} else if (agent.indexOf("gecko") != -1 && agent.indexOf("rv:11") != -1) {
+					return agent;
+				}
+			}
+			return UNKNOWN;
+		} catch (Exception e) {
+			return "";
+		}
+
+	}
+
+	/**
+	 * 获得当天是周几
+	 */
+	public static String getWeekDay() {
+		String[] weekDays = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(new Date());
+
+		int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
+		if (w < 0) {
+			w = 0;
+		}
+		return weekDays[w];
+	}
+
+	/**
+	 * 获取当前机器的IP
+	 *
+	 * @return /
+	 */
+	public static String getLocalIp() {
+		try {
+			InetAddress candidateAddress = null;
+			// 遍历所有的网络接口
+			for (Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); interfaces
+					.hasMoreElements();) {
+				NetworkInterface anInterface = interfaces.nextElement();
+				// 在所有的接口下再遍历IP
+				for (Enumeration<InetAddress> inetAddresses = anInterface.getInetAddresses(); inetAddresses
+						.hasMoreElements();) {
+					InetAddress inetAddr = inetAddresses.nextElement();
+					// 排除loopback类型地址
+					if (!inetAddr.isLoopbackAddress()) {
+						if (inetAddr.isSiteLocalAddress()) {
+							// 如果是site-local地址,就是它了
+							return inetAddr.getHostAddress();
+						} else if (candidateAddress == null) {
+							// site-local类型的地址未被发现,先记录候选地址
+							candidateAddress = inetAddr;
+						}
+					}
+				}
+			}
+			if (candidateAddress != null) {
+				return candidateAddress.getHostAddress();
+			}
+			// 如果没有发现 non-loopback地址.只能用最次选的方案
+			InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
+			if (jdkSuppliedAddress == null) {
+				return "";
+			}
+			return jdkSuppliedAddress.getHostAddress();
+		} catch (Exception e) {
+			return "";
+		}
+	}
+}

+ 37 - 0
eladmin-common/src/main/java/me/zhengjie/utils/ThrowableUtil.java

@@ -0,0 +1,37 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * 异常工具 2019-01-06
+ * @author Zheng Jie
+ */
+public class ThrowableUtil {
+
+    /**
+     * 获取堆栈信息
+     */
+    public static String getStackTrace(Throwable throwable){
+        StringWriter sw = new StringWriter();
+        try (PrintWriter pw = new PrintWriter(sw)) {
+            throwable.printStackTrace(pw);
+            return sw.toString();
+        }
+    }
+}

+ 66 - 0
eladmin-common/src/main/java/me/zhengjie/utils/TranslatorUtil.java

@@ -0,0 +1,66 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import cn.hutool.json.JSONArray;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+
+/**
+ * @author Zheng Jie
+ * 翻译工具类
+ */
+public class TranslatorUtil {
+
+    public static String translate(String word){
+        try {
+            String url = "https://translate.googleapis.com/translate_a/single?" +
+                    "client=gtx&" +
+                    "sl=en" +
+                    "&tl=zh-CN" +
+                    "&dt=t&q=" + URLEncoder.encode(word, "UTF-8");
+
+            URL obj = new URL(url);
+            HttpURLConnection con = (HttpURLConnection) obj.openConnection();
+            con.setRequestProperty("User-Agent", "Mozilla/5.0");
+
+            BufferedReader in = new BufferedReader(
+                    new InputStreamReader(con.getInputStream()));
+            String inputLine;
+            StringBuilder response = new StringBuilder();
+
+            while ((inputLine = in.readLine()) != null) {
+                response.append(inputLine);
+            }
+            in.close();
+            return parseResult(response.toString());
+        }catch (Exception e){
+          return  word;
+        }
+    }
+
+    private static String parseResult(String inputJson){
+        JSONArray jsonArray2 = (JSONArray) new JSONArray(inputJson).get(0);
+        StringBuilder result = new StringBuilder();
+        for (Object o : jsonArray2) {
+            result.append(((JSONArray) o).get(0).toString());
+        }
+        return result.toString();
+    }
+}

+ 45 - 0
eladmin-common/src/main/java/me/zhengjie/utils/ValidationUtil.java

@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import cn.hutool.core.util.ObjectUtil;
+import me.zhengjie.exception.BadRequestException;
+import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator;
+
+/**
+ * 验证工具
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+public class ValidationUtil{
+
+    /**
+     * 验证空
+     */
+    public static void isNull(Object obj, String entity, String parameter , Object value){
+        if(ObjectUtil.isNull(obj)){
+            String msg = entity + " 不存在: "+ parameter +" is "+ value;
+            throw new BadRequestException(msg);
+        }
+    }
+
+    /**
+     * 验证是否为邮箱
+     */
+    public static boolean isEmail(String email) {
+        return new EmailValidator().isValid(email, null);
+    }
+}

+ 50 - 0
eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeBiEnum.java

@@ -0,0 +1,50 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * <p>
+ * 验证码业务场景
+ * </p>
+ * @author Zheng Jie
+ * @date 2020-05-02
+ */
+@Getter
+@AllArgsConstructor
+public enum CodeBiEnum {
+
+    /* 旧邮箱修改邮箱 */
+    ONE(1, "旧邮箱修改邮箱"),
+
+    /* 通过邮箱修改密码 */
+    TWO(2, "通过邮箱修改密码");
+
+    private final Integer code;
+    private final String description;
+
+    public static CodeBiEnum find(Integer code) {
+        for (CodeBiEnum value : CodeBiEnum.values()) {
+            if (code.equals(value.getCode())) {
+                return value;
+            }
+        }
+        return null;
+    }
+
+}

+ 46 - 0
eladmin-common/src/main/java/me/zhengjie/utils/enums/CodeEnum.java

@@ -0,0 +1,46 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * <p>
+ * 验证码业务场景对应的 Redis 中的 key
+ * </p>
+ * @author Zheng Jie
+ * @date 2020-05-02
+ */
+@Getter
+@AllArgsConstructor
+public enum CodeEnum {
+
+    /* 通过手机号码重置邮箱 */
+    PHONE_RESET_EMAIL_CODE("phone_reset_email_code_", "通过手机号码重置邮箱"),
+
+    /* 通过旧邮箱重置邮箱 */
+    EMAIL_RESET_EMAIL_CODE("email_reset_email_code_", "通过旧邮箱重置邮箱"),
+
+    /* 通过手机号码重置密码 */
+    PHONE_RESET_PWD_CODE("phone_reset_pwd_code_", "通过手机号码重置密码"),
+
+    /* 通过邮箱重置密码 */
+    EMAIL_RESET_PWD_CODE("email_reset_pwd_code_", "通过邮箱重置密码");
+
+    private final String key;
+    private final String description;
+}

+ 53 - 0
eladmin-common/src/main/java/me/zhengjie/utils/enums/DataScopeEnum.java

@@ -0,0 +1,53 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * <p>
+ * 数据权限枚举
+ * </p>
+ * @author Zheng Jie
+ * @date 2020-05-07
+ */
+@Getter
+@AllArgsConstructor
+public enum DataScopeEnum {
+
+    /* 全部的数据权限 */
+    ALL("全部", "全部的数据权限"),
+
+    /* 自己部门的数据权限 */
+    THIS_LEVEL("本级", "自己部门的数据权限"),
+
+    /* 自定义的数据权限 */
+    CUSTOMIZE("自定义", "自定义的数据权限");
+
+    private final String value;
+    private final String description;
+
+    public static DataScopeEnum find(String val) {
+        for (DataScopeEnum dataScopeEnum : DataScopeEnum.values()) {
+            if (val.equals(dataScopeEnum.getValue())) {
+                return dataScopeEnum;
+            }
+        }
+        return null;
+    }
+
+}

+ 74 - 0
eladmin-common/src/main/java/me/zhengjie/utils/enums/RequestMethodEnum.java

@@ -0,0 +1,74 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author Zheng Jie
+ * @website https://el-admin.vip
+ * @description
+ * @date 2020-06-10
+ **/
+@Getter
+@AllArgsConstructor
+public enum RequestMethodEnum {
+
+    /**
+     * 搜寻 @AnonymousGetMapping
+     */
+    GET("GET"),
+
+    /**
+     * 搜寻 @AnonymousPostMapping
+     */
+    POST("POST"),
+
+    /**
+     * 搜寻 @AnonymousPutMapping
+     */
+    PUT("PUT"),
+
+    /**
+     * 搜寻 @AnonymousPatchMapping
+     */
+    PATCH("PATCH"),
+
+    /**
+     * 搜寻 @AnonymousDeleteMapping
+     */
+    DELETE("DELETE"),
+
+    /**
+     * 否则就是所有 Request 接口都放行
+     */
+    ALL("All");
+
+    /**
+     * Request 类型
+     */
+    private final String type;
+
+    public static RequestMethodEnum find(String type) {
+        for (RequestMethodEnum value : RequestMethodEnum.values()) {
+            if (type.equals(value.getType())) {
+                return value;
+            }
+        }
+        return ALL;
+    }
+}

+ 31 - 0
eladmin-common/src/main/java/me/zhengjie/websocket/MsgType.java

@@ -0,0 +1,31 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.websocket;
+
+/**
+ * @author ZhangHouYing
+ * @date 2019-08-10 9:56
+ */
+public enum MsgType {
+	/** 连接 */
+	CONNECT,
+	/** 关闭 */
+	CLOSE,
+	/** 信息 */
+	INFO,
+	/** 错误 */
+	ERROR
+}

+ 33 - 0
eladmin-common/src/main/java/me/zhengjie/websocket/SocketMsg.java

@@ -0,0 +1,33 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.websocket;
+
+import lombok.Data;
+
+/**
+ * @author ZhangHouYing
+ * @date 2019-08-10 9:55
+ */
+@Data
+public class SocketMsg {
+	private String msg;
+	private MsgType msgType;
+
+	public SocketMsg(String msg, MsgType msgType) {
+		this.msg = msg;
+		this.msgType = msgType;
+	}
+}

+ 164 - 0
eladmin-common/src/main/java/me/zhengjie/websocket/WebSocketServer.java

@@ -0,0 +1,164 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.websocket;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.websocket.*;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArraySet;
+/**
+ * @author ZhangHouYing
+ * @date 2019-08-10 15:46
+ */
+@ServerEndpoint("/webSocket/{sid}")
+@Slf4j
+@Component
+public class WebSocketServer {
+
+	/**
+	 * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
+	 */
+	private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
+
+	/**
+	 * 与某个客户端的连接会话,需要通过它来给客户端发送数据
+	 */
+	private Session session;
+
+	private static int onlineCount = 0;
+
+	/**
+	 * 接收sid
+	 */
+	private String sid="";
+	/**
+	 * 连接建立成功调用的方法
+	 * */
+	@OnOpen
+	public void onOpen(Session session,@PathParam("sid") String sid) {
+		this.session = session;
+		//如果存在就先删除一个,防止重复推送消息
+		for (WebSocketServer webSocket:webSocketSet) {
+			if (webSocket.sid.equals(sid)) {
+				webSocketSet.remove(webSocket);
+			}
+		}
+		webSocketSet.add(this);
+		addOnlineCount(); //在线数加1
+		System.out.println("有新窗口开始监听:" + sid + "当前人数:" + getOnlineCount());
+		this.sid=sid;
+		try{
+			sendMessage("连接成功");
+		}catch (IOException e) {
+			System.out.println("websocket 异常");
+		}
+	}
+
+	public static synchronized int getOnlineCount(){
+		return onlineCount;
+
+	}
+
+	public static synchronized void addOnlineCount(){
+		WebSocketServer.onlineCount++;
+	}
+
+	public static synchronized void subOnlineCount(){
+		WebSocketServer.onlineCount--;
+	}
+
+
+	/**
+	 * 连接关闭调用的方法
+	 */
+	@OnClose
+	public void onClose() {
+		webSocketSet.remove(this);
+		subOnlineCount();
+		System.out.println("有一个连接关闭,当前人数:" + getOnlineCount());
+	}
+
+	/**
+	 * 收到客户端消息后调用的方法
+	 * @param message 客户端发送过来的消息*/
+	@OnMessage
+	public void onMessage(String message, Session session) {
+		log.info("收到来"+sid+"的信息:"+message);
+		//群发消息
+		for (WebSocketServer item : webSocketSet) {
+			try {
+				item.sendMessage(message);
+			} catch (IOException e) {
+				log.error(e.getMessage(),e);
+			}
+		}
+	}
+
+	@OnError
+	public void onError(Session session, Throwable error) {
+		log.error("发生错误");
+		error.printStackTrace();
+	}
+	/**
+	 * 实现服务器主动推送
+	 */
+	private void sendMessage(String message) throws IOException {
+		this.session.getBasicRemote().sendText(message);
+	}
+
+
+	/**
+	 * 群发自定义消息
+	 * */
+	public static void sendInfo(SocketMsg socketMsg, @PathParam("sid") String sid) throws IOException {
+		String message = JSONObject.toJSONString(socketMsg);
+		log.info("推送消息到"+sid+",推送内容:"+message);
+		for (WebSocketServer item : webSocketSet) {
+			try {
+				//这里可以设定只推送给这个sid的,为null则全部推送
+				if(sid==null) {
+					item.sendMessage(message);
+				}else if(item.sid.equals(sid)){
+					item.sendMessage(message);
+				}
+			} catch (IOException ignored) { }
+		}
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) {
+			return true;
+		}
+		if (o == null || getClass() != o.getClass()) {
+			return false;
+		}
+		WebSocketServer that = (WebSocketServer) o;
+		return Objects.equals(session, that.session) &&
+				Objects.equals(sid, that.sid);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(session, sid);
+	}
+}

+ 26 - 0
eladmin-common/src/test/java/me/zhengjie/utils/DateUtilsTest.java

@@ -0,0 +1,26 @@
+package me.zhengjie.utils;
+
+import org.junit.Test;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+public class DateUtilsTest {
+    @Test
+    public void test1() {
+        long l = System.currentTimeMillis() / 1000;
+        LocalDateTime localDateTime = DateUtil.fromTimeStamp(l);
+        System.out.print(DateUtil.localDateTimeFormatyMdHms(localDateTime));
+    }
+
+    @Test
+    public void test2() {
+        LocalDateTime now = LocalDateTime.now();
+        System.out.println(DateUtil.localDateTimeFormatyMdHms(now));
+        Date date = DateUtil.toDate(now);
+        LocalDateTime localDateTime = DateUtil.toLocalDateTime(date);
+        System.out.println(DateUtil.localDateTimeFormatyMdHms(localDateTime));
+        LocalDateTime localDateTime1 = DateUtil.fromTimeStamp(date.getTime() / 1000);
+        System.out.println(DateUtil.localDateTimeFormatyMdHms(localDateTime1));
+    }
+}

+ 32 - 0
eladmin-common/src/test/java/me/zhengjie/utils/EncryptUtilsTest.java

@@ -0,0 +1,32 @@
+package me.zhengjie.utils;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static me.zhengjie.utils.EncryptUtils.*;
+
+public class EncryptUtilsTest {
+
+    /**
+     * 对称加密
+     */
+    @Test
+    public void testDesEncrypt() {
+        try {
+            assertEquals("7772841DC6099402", desEncrypt("123456"));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 对称解密
+     */
+    @Test
+    public void testDesDecrypt() {
+        try {
+            assertEquals("123456", desDecrypt("7772841DC6099402"));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 36 - 0
eladmin-common/src/test/java/me/zhengjie/utils/FileUtilTest.java

@@ -0,0 +1,36 @@
+package me.zhengjie.utils;
+
+import org.junit.Test;
+import org.springframework.mock.web.MockMultipartFile;
+
+import static org.junit.Assert.*;
+import static me.zhengjie.utils.FileUtil.*;
+
+public class FileUtilTest {
+
+    @Test
+    public void testToFile() {
+        long retval = toFile(new MockMultipartFile("foo", (byte[]) null)).getTotalSpace();
+        assertEquals(500695072768L, retval);
+    }
+
+    @Test
+    public void testGetExtensionName() {
+        assertEquals("foo", getExtensionName("foo"));
+        assertEquals("exe", getExtensionName("bar.exe"));
+    }
+
+    @Test
+    public void testGetFileNameNoEx() {
+        assertEquals("foo", getFileNameNoEx("foo"));
+        assertEquals("bar", getFileNameNoEx("bar.txt"));
+    }
+
+    @Test
+    public void testGetSize() {
+        assertEquals("1000B   ", getSize(1000));
+        assertEquals("1.00KB   ", getSize(1024));
+        assertEquals("1.00MB   ", getSize(1048576));
+        assertEquals("1.00GB   ", getSize(1073741824));
+    }
+}

+ 43 - 0
eladmin-common/src/test/java/me/zhengjie/utils/StringUtilsTest.java

@@ -0,0 +1,43 @@
+package me.zhengjie.utils;
+
+import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+import static me.zhengjie.utils.StringUtils.*;
+import static org.junit.Assert.*;
+
+public class StringUtilsTest {
+
+    @Test
+    public void testToCamelCase() {
+        assertNull(toCamelCase(null));
+    }
+
+    @Test
+    public void testToCapitalizeCamelCase() {
+        assertNull(StringUtils.toCapitalizeCamelCase(null));
+        assertEquals("HelloWorld", toCapitalizeCamelCase("hello_world"));
+    }
+
+    @Test
+    public void testToUnderScoreCase() {
+        assertNull(StringUtils.toUnderScoreCase(null));
+        assertEquals("hello_world", toUnderScoreCase("helloWorld"));
+        assertEquals("\u0000\u0000", toUnderScoreCase("\u0000\u0000"));
+        assertEquals("\u0000_a", toUnderScoreCase("\u0000A"));
+    }
+
+    @Test
+    public void testGetWeekDay() {
+        SimpleDateFormat simpleDateformat = new SimpleDateFormat("E");
+        assertEquals(simpleDateformat.format(new Date()), getWeekDay());
+    }
+
+    @Test
+    public void testGetIP() {
+        assertEquals("127.0.0.1", getIp(new MockHttpServletRequest()));
+    }
+}

+ 39 - 0
eladmin-generator/pom.xml

@@ -0,0 +1,39 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>eladmin</artifactId>
+        <groupId>me.zhengjie</groupId>
+        <version>2.6</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>eladmin-generator</artifactId>
+    <name>代码生成模块</name>
+
+    <properties>
+        <configuration.version>1.9</configuration.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>me.zhengjie</groupId>
+            <artifactId>eladmin-common</artifactId>
+            <version>2.6</version>
+        </dependency>
+
+        <!--模板引擎-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-freemarker</artifactId>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/commons-configuration/commons-configuration -->
+        <dependency>
+            <groupId>commons-configuration</groupId>
+            <artifactId>commons-configuration</artifactId>
+            <version>${configuration.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 97 - 0
eladmin-generator/src/main/java/me/zhengjie/domain/ColumnInfo.java

@@ -0,0 +1,97 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.domain;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import me.zhengjie.utils.GenUtil;
+import javax.persistence.*;
+import java.io.Serializable;
+
+/**
+ * 列的数据信息
+ * @author Zheng Jie
+ * @date 2019-01-02
+ */
+@Getter
+@Setter
+@Entity
+@NoArgsConstructor
+@Table(name = "code_column_config")
+public class ColumnInfo implements Serializable {
+
+    @Id
+    @Column(name = "column_id")
+    @ApiModelProperty(value = "ID", hidden = true)
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @ApiModelProperty(value = "表名")
+    private String tableName;
+
+    @ApiModelProperty(value = "数据库字段名称")
+    private String columnName;
+
+    @ApiModelProperty(value = "数据库字段类型")
+    private String columnType;
+
+    @ApiModelProperty(value = "数据库字段键类型")
+    private String keyType;
+
+    @ApiModelProperty(value = "字段额外的参数")
+    private String extra;
+
+    @ApiModelProperty(value = "数据库字段描述")
+    private String remark;
+
+    @ApiModelProperty(value = "是否必填")
+    private Boolean notNull;
+
+    @ApiModelProperty(value = "是否在列表显示")
+    private Boolean listShow;
+
+    @ApiModelProperty(value = "是否表单显示")
+    private Boolean formShow;
+
+    @ApiModelProperty(value = "表单类型")
+    private String formType;
+
+    @ApiModelProperty(value = "查询 1:模糊 2:精确")
+    private String queryType;
+
+    @ApiModelProperty(value = "字典名称")
+    private String dictName;
+
+    @ApiModelProperty(value = "日期注解")
+    private String dateAnnotation;
+
+    public ColumnInfo(String tableName, String columnName, Boolean notNull, String columnType, String remark, String keyType, String extra) {
+        this.tableName = tableName;
+        this.columnName = columnName;
+        this.columnType = columnType;
+        this.keyType = keyType;
+        this.extra = extra;
+        this.notNull = notNull;
+        if(GenUtil.PK.equalsIgnoreCase(keyType) && GenUtil.EXTRA.equalsIgnoreCase(extra)){
+            this.notNull = false;
+        }
+        this.remark = remark;
+        this.listShow = true;
+        this.formShow = true;
+    }
+}

+ 78 - 0
eladmin-generator/src/main/java/me/zhengjie/domain/GenConfig.java

@@ -0,0 +1,78 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.domain;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+/**
+ * 代码生成配置
+ * @author Zheng Jie
+ * @date 2019-01-03
+ */
+@Getter
+@Setter
+@Entity
+@NoArgsConstructor
+@Table(name = "code_gen_config")
+public class GenConfig implements Serializable {
+
+    public GenConfig(String tableName) {
+        this.tableName = tableName;
+    }
+
+    @Id
+    @Column(name = "config_id")
+    @ApiModelProperty(value = "ID", hidden = true)
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @NotBlank
+    @ApiModelProperty(value = "表名")
+    private String tableName;
+
+    @ApiModelProperty(value = "接口名称")
+    private String apiAlias;
+
+    @NotBlank
+    @ApiModelProperty(value = "包路径")
+    private String pack;
+
+    @NotBlank
+    @ApiModelProperty(value = "模块名")
+    private String moduleName;
+
+    @NotBlank
+    @ApiModelProperty(value = "前端文件路径")
+    private String path;
+
+    @ApiModelProperty(value = "前端文件路径")
+    private String apiPath;
+
+    @ApiModelProperty(value = "作者")
+    private String author;
+
+    @ApiModelProperty(value = "表前缀")
+    private String prefix;
+
+    @ApiModelProperty(value = "是否覆盖")
+    private Boolean cover = false;
+}

+ 48 - 0
eladmin-generator/src/main/java/me/zhengjie/domain/vo/TableInfo.java

@@ -0,0 +1,48 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.domain.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 表的数据信息
+ * @author Zheng Jie
+ * @date 2019-01-02
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class TableInfo {
+
+    /** 表名称 */
+    private Object tableName;
+
+    /** 创建日期 */
+    private Object createTime;
+
+    /** 数据库引擎 */
+    private Object engine;
+
+    /** 编码集 */
+    private Object coding;
+
+    /** 备注 */
+    private Object remark;
+
+
+}

+ 34 - 0
eladmin-generator/src/main/java/me/zhengjie/repository/ColumnInfoRepository.java

@@ -0,0 +1,34 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.repository;
+
+import me.zhengjie.domain.ColumnInfo;
+import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.List;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-01-14
+ */
+public interface ColumnInfoRepository extends JpaRepository<ColumnInfo,Long> {
+
+    /**
+     * 查询表信息
+     * @param tableName 表格名
+     * @return 表信息
+     */
+    List<ColumnInfo> findByTableNameOrderByIdAsc(String tableName);
+}

+ 33 - 0
eladmin-generator/src/main/java/me/zhengjie/repository/GenConfigRepository.java

@@ -0,0 +1,33 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.repository;
+
+import me.zhengjie.domain.GenConfig;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-01-14
+ */
+public interface GenConfigRepository extends JpaRepository<GenConfig,Long> {
+
+    /**
+     * 查询表配置
+     * @param tableName 表名
+     * @return /
+     */
+    GenConfig findByTableName(String tableName);
+}

+ 51 - 0
eladmin-generator/src/main/java/me/zhengjie/rest/GenConfigController.java

@@ -0,0 +1,51 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.rest;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import me.zhengjie.domain.GenConfig;
+import me.zhengjie.service.GenConfigService;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-01-14
+ */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/genConfig")
+@Api(tags = "系统:代码生成器配置管理")
+public class GenConfigController {
+
+    private final GenConfigService genConfigService;
+
+    @ApiOperation("查询")
+    @GetMapping(value = "/{tableName}")
+    public ResponseEntity<Object> queryGenConfig(@PathVariable String tableName){
+        return new ResponseEntity<>(genConfigService.find(tableName), HttpStatus.OK);
+    }
+
+    @PutMapping
+    @ApiOperation("修改")
+    public ResponseEntity<Object> updateGenConfig(@Validated @RequestBody GenConfig genConfig){
+        return new ResponseEntity<>(genConfigService.update(genConfig.getTableName(), genConfig),HttpStatus.OK);
+    }
+}

+ 107 - 0
eladmin-generator/src/main/java/me/zhengjie/rest/GeneratorController.java

@@ -0,0 +1,107 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.rest;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import me.zhengjie.domain.ColumnInfo;
+import me.zhengjie.exception.BadRequestException;
+import me.zhengjie.service.GenConfigService;
+import me.zhengjie.service.GeneratorService;
+import me.zhengjie.utils.PageUtil;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-01-02
+ */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/generator")
+@Api(tags = "系统:代码生成管理")
+public class GeneratorController {
+
+    private final GeneratorService generatorService;
+    private final GenConfigService genConfigService;
+
+    @Value("${generator.enabled}")
+    private Boolean generatorEnabled;
+
+    @ApiOperation("查询数据库数据")
+    @GetMapping(value = "/tables/all")
+    public ResponseEntity<Object> queryAllTables(){
+        return new ResponseEntity<>(generatorService.getTables(), HttpStatus.OK);
+    }
+
+    @ApiOperation("查询数据库数据")
+    @GetMapping(value = "/tables")
+    public ResponseEntity<Object> queryTables(@RequestParam(defaultValue = "") String name,
+                                    @RequestParam(defaultValue = "0")Integer page,
+                                    @RequestParam(defaultValue = "10")Integer size){
+        int[] startEnd = PageUtil.transToStartEnd(page, size);
+        return new ResponseEntity<>(generatorService.getTables(name,startEnd), HttpStatus.OK);
+    }
+
+    @ApiOperation("查询字段数据")
+    @GetMapping(value = "/columns")
+    public ResponseEntity<Object> queryColumns(@RequestParam String tableName){
+        List<ColumnInfo> columnInfos = generatorService.getColumns(tableName);
+        return new ResponseEntity<>(PageUtil.toPage(columnInfos,columnInfos.size()), HttpStatus.OK);
+    }
+
+    @ApiOperation("保存字段数据")
+    @PutMapping
+    public ResponseEntity<HttpStatus> saveColumn(@RequestBody List<ColumnInfo> columnInfos){
+        generatorService.save(columnInfos);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation("同步字段数据")
+    @PostMapping(value = "sync")
+    public ResponseEntity<HttpStatus> syncColumn(@RequestBody List<String> tables){
+        for (String table : tables) {
+            generatorService.sync(generatorService.getColumns(table), generatorService.query(table));
+        }
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation("生成代码")
+    @PostMapping(value = "/{tableName}/{type}")
+    public ResponseEntity<Object> generatorCode(@PathVariable String tableName, @PathVariable Integer type, HttpServletRequest request, HttpServletResponse response){
+        if(!generatorEnabled && type == 0){
+            throw new BadRequestException("此环境不允许生成代码,请选择预览或者下载查看!");
+        }
+        switch (type){
+            // 生成代码
+            case 0: generatorService.generator(genConfigService.find(tableName), generatorService.getColumns(tableName));
+                    break;
+            // 预览
+            case 1: return generatorService.preview(genConfigService.find(tableName), generatorService.getColumns(tableName));
+            // 打包
+            case 2: generatorService.download(genConfigService.find(tableName), generatorService.getColumns(tableName), request, response);
+                    break;
+            default: throw new BadRequestException("没有这个选项");
+        }
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}

+ 40 - 0
eladmin-generator/src/main/java/me/zhengjie/service/GenConfigService.java

@@ -0,0 +1,40 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.service;
+
+import me.zhengjie.domain.GenConfig;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-01-14
+ */
+public interface GenConfigService {
+
+    /**
+     * 查询表配置
+     * @param tableName 表名
+     * @return 表配置
+     */
+    GenConfig find(String tableName);
+
+    /**
+     * 更新表配置
+     * @param tableName 表名
+     * @param genConfig 表配置
+     * @return 表配置
+     */
+    GenConfig update(String tableName, GenConfig genConfig);
+}

+ 96 - 0
eladmin-generator/src/main/java/me/zhengjie/service/GeneratorService.java

@@ -0,0 +1,96 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.service;
+
+import me.zhengjie.domain.GenConfig;
+import me.zhengjie.domain.ColumnInfo;
+import org.springframework.http.ResponseEntity;
+import org.springframework.scheduling.annotation.Async;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-01-02
+ */
+public interface GeneratorService {
+
+    /**
+     * 查询数据库元数据
+     * @param name 表名
+     * @param startEnd 分页参数
+     * @return /
+     */
+    Object getTables(String name, int[] startEnd);
+
+    /**
+     * 得到数据表的元数据
+     * @param name 表名
+     * @return /
+     */
+    List<ColumnInfo> getColumns(String name);
+
+    /**
+     * 同步表数据
+     * @param columnInfos /
+     * @param columnInfoList /
+     */
+    void sync(List<ColumnInfo> columnInfos, List<ColumnInfo> columnInfoList);
+
+    /**
+     * 保持数据
+     * @param columnInfos /
+     */
+    void save(List<ColumnInfo> columnInfos);
+
+    /**
+     * 获取所有table
+     * @return /
+     */
+    Object getTables();
+
+    /**
+     * 代码生成
+     * @param genConfig 配置信息
+     * @param columns 字段信息
+     */
+    void generator(GenConfig genConfig, List<ColumnInfo> columns);
+
+    /**
+     * 预览
+     * @param genConfig 配置信息
+     * @param columns 字段信息
+     * @return /
+     */
+    ResponseEntity<Object> preview(GenConfig genConfig, List<ColumnInfo> columns);
+
+    /**
+     * 打包下载
+     * @param genConfig 配置信息
+     * @param columns 字段信息
+     * @param request /
+     * @param response /
+     */
+    void download(GenConfig genConfig, List<ColumnInfo> columns, HttpServletRequest request, HttpServletResponse response);
+
+    /**
+     * 查询数据库的表字段数据数据
+     * @param table /
+     * @return /
+     */
+    List<ColumnInfo> query(String table);
+}

+ 67 - 0
eladmin-generator/src/main/java/me/zhengjie/service/impl/GenConfigServiceImpl.java

@@ -0,0 +1,67 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import me.zhengjie.domain.GenConfig;
+import me.zhengjie.repository.GenConfigRepository;
+import me.zhengjie.service.GenConfigService;
+import me.zhengjie.utils.StringUtils;
+import org.springframework.stereotype.Service;
+import java.io.File;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-01-14
+ */
+@Service
+@RequiredArgsConstructor
+public class GenConfigServiceImpl implements GenConfigService {
+
+    private final GenConfigRepository genConfigRepository;
+
+    @Override
+    public GenConfig find(String tableName) {
+        GenConfig genConfig = genConfigRepository.findByTableName(tableName);
+        if(genConfig == null){
+            return new GenConfig(tableName);
+        }
+        return genConfig;
+    }
+
+    @Override
+    public GenConfig update(String tableName, GenConfig genConfig) {
+        String separator = File.separator;
+        String[] paths;
+        String symbol = "\\";
+        if (symbol.equals(separator)) {
+            paths = genConfig.getPath().split("\\\\");
+        } else {
+            paths = genConfig.getPath().split(File.separator);
+        }
+        StringBuilder api = new StringBuilder();
+        for (String path : paths) {
+            api.append(path);
+            api.append(separator);
+            if ("src".equals(path)) {
+                api.append("api");
+                break;
+            }
+        }
+        genConfig.setApiPath(api.toString());
+        return genConfigRepository.save(genConfig);
+    }
+}

+ 205 - 0
eladmin-generator/src/main/java/me/zhengjie/service/impl/GeneratorServiceImpl.java

@@ -0,0 +1,205 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.ZipUtil;
+import lombok.RequiredArgsConstructor;
+import me.zhengjie.domain.GenConfig;
+import me.zhengjie.domain.ColumnInfo;
+import me.zhengjie.domain.vo.TableInfo;
+import me.zhengjie.exception.BadRequestException;
+import me.zhengjie.repository.ColumnInfoRepository;
+import me.zhengjie.service.GeneratorService;
+import me.zhengjie.utils.FileUtil;
+import me.zhengjie.utils.GenUtil;
+import me.zhengjie.utils.PageUtil;
+import me.zhengjie.utils.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-01-02
+ */
+@Service
+@RequiredArgsConstructor
+public class GeneratorServiceImpl implements GeneratorService {
+    private static final Logger log = LoggerFactory.getLogger(GeneratorServiceImpl.class);
+    @PersistenceContext
+    private EntityManager em;
+
+    private final ColumnInfoRepository columnInfoRepository;
+
+    @Override
+    public Object getTables() {
+        // 使用预编译防止sql注入
+        String sql = "select table_name ,create_time , engine, table_collation, table_comment from information_schema.tables " +
+                "where table_schema = (select database()) " +
+                "order by create_time desc";
+        Query query = em.createNativeQuery(sql);
+        return query.getResultList();
+    }
+
+    @Override
+    public Object getTables(String name, int[] startEnd) {
+        // 使用预编译防止sql注入
+        String sql = "select table_name ,create_time , engine, table_collation, table_comment from information_schema.tables " +
+                "where table_schema = (select database()) " +
+                "and table_name like :table order by create_time desc";
+        Query query = em.createNativeQuery(sql);
+        query.setFirstResult(startEnd[0]);
+        query.setMaxResults(startEnd[1] - startEnd[0]);
+        query.setParameter("table", StringUtils.isNotBlank(name) ? ("%" + name + "%") : "%%");
+        List result = query.getResultList();
+        List<TableInfo> tableInfos = new ArrayList<>();
+        for (Object obj : result) {
+            Object[] arr = (Object[]) obj;
+            tableInfos.add(new TableInfo(arr[0], arr[1], arr[2], arr[3], ObjectUtil.isNotEmpty(arr[4]) ? arr[4] : "-"));
+        }
+        String countSql = "select count(1) from information_schema.tables " +
+                "where table_schema = (select database()) and table_name like :table";
+        Query queryCount = em.createNativeQuery(countSql);
+        queryCount.setParameter("table", StringUtils.isNotBlank(name) ? ("%" + name + "%") : "%%");
+        Object totalElements = queryCount.getSingleResult();
+        return PageUtil.toPage(tableInfos, totalElements);
+    }
+
+    @Override
+    public List<ColumnInfo> getColumns(String tableName) {
+        List<ColumnInfo> columnInfos = columnInfoRepository.findByTableNameOrderByIdAsc(tableName);
+        if (CollectionUtil.isNotEmpty(columnInfos)) {
+            return columnInfos;
+        } else {
+            columnInfos = query(tableName);
+            return columnInfoRepository.saveAll(columnInfos);
+        }
+    }
+
+    @Override
+    public List<ColumnInfo> query(String tableName) {
+        // 使用预编译防止sql注入
+        String sql = "select column_name, is_nullable, data_type, column_comment, column_key, extra from information_schema.columns " +
+                "where table_name = ? and table_schema = (select database()) order by ordinal_position";
+        Query query = em.createNativeQuery(sql);
+        query.setParameter(1, tableName);
+        List result = query.getResultList();
+        List<ColumnInfo> columnInfos = new ArrayList<>();
+        for (Object obj : result) {
+            Object[] arr = (Object[]) obj;
+            columnInfos.add(
+                    new ColumnInfo(
+                            tableName,
+                            arr[0].toString(),
+                            "NO".equals(arr[1]),
+                            arr[2].toString(),
+                            ObjectUtil.isNotNull(arr[3]) ? arr[3].toString() : null,
+                            ObjectUtil.isNotNull(arr[4]) ? arr[4].toString() : null,
+                            ObjectUtil.isNotNull(arr[5]) ? arr[5].toString() : null)
+            );
+        }
+        return columnInfos;
+    }
+
+    @Override
+    public void sync(List<ColumnInfo> columnInfos, List<ColumnInfo> columnInfoList) {
+        // 第一种情况,数据库类字段改变或者新增字段
+        for (ColumnInfo columnInfo : columnInfoList) {
+            // 根据字段名称查找
+            List<ColumnInfo> columns = columnInfos.stream().filter(c -> c.getColumnName().equals(columnInfo.getColumnName())).collect(Collectors.toList());
+            // 如果能找到,就修改部分可能被字段
+            if (CollectionUtil.isNotEmpty(columns)) {
+                ColumnInfo column = columns.get(0);
+                column.setColumnType(columnInfo.getColumnType());
+                column.setExtra(columnInfo.getExtra());
+                column.setKeyType(columnInfo.getKeyType());
+                if (StringUtils.isBlank(column.getRemark())) {
+                    column.setRemark(columnInfo.getRemark());
+                }
+                columnInfoRepository.save(column);
+            } else {
+                // 如果找不到,则保存新字段信息
+                columnInfoRepository.save(columnInfo);
+            }
+        }
+        // 第二种情况,数据库字段删除了
+        for (ColumnInfo columnInfo : columnInfos) {
+            // 根据字段名称查找
+            List<ColumnInfo> columns = columnInfoList.stream().filter(c -> c.getColumnName().equals(columnInfo.getColumnName())).collect(Collectors.toList());
+            // 如果找不到,就代表字段被删除了,则需要删除该字段
+            if (CollectionUtil.isEmpty(columns)) {
+                columnInfoRepository.delete(columnInfo);
+            }
+        }
+    }
+
+    @Override
+    public void save(List<ColumnInfo> columnInfos) {
+        columnInfoRepository.saveAll(columnInfos);
+    }
+
+    @Override
+    public void generator(GenConfig genConfig, List<ColumnInfo> columns) {
+        if (genConfig.getId() == null) {
+            throw new BadRequestException("请先配置生成器");
+        }
+        try {
+            GenUtil.generatorCode(columns, genConfig);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+            throw new BadRequestException("生成失败,请手动处理已生成的文件");
+        }
+    }
+
+    @Override
+    public ResponseEntity<Object> preview(GenConfig genConfig, List<ColumnInfo> columns) {
+        if (genConfig.getId() == null) {
+            throw new BadRequestException("请先配置生成器");
+        }
+        List<Map<String, Object>> genList = GenUtil.preview(columns, genConfig);
+        return new ResponseEntity<>(genList, HttpStatus.OK);
+    }
+
+    @Override
+    public void download(GenConfig genConfig, List<ColumnInfo> columns, HttpServletRequest request, HttpServletResponse response) {
+        if (genConfig.getId() == null) {
+            throw new BadRequestException("请先配置生成器");
+        }
+        try {
+            File file = new File(GenUtil.download(columns, genConfig));
+            String zipPath = file.getPath() + ".zip";
+            ZipUtil.zip(file.getPath(), zipPath);
+            FileUtil.downloadFile(request, response, new File(zipPath), true);
+        } catch (IOException e) {
+            throw new BadRequestException("打包失败");
+        }
+    }
+}

+ 54 - 0
eladmin-generator/src/main/java/me/zhengjie/utils/ColUtil.java

@@ -0,0 +1,54 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import org.apache.commons.configuration.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * sql字段转java
+ *
+ * @author Zheng Jie
+ * @date 2019-01-03
+ */
+public class ColUtil {
+    private static final Logger log = LoggerFactory.getLogger(ColUtil.class);
+
+    /**
+     * 转换mysql数据类型为java数据类型
+     *
+     * @param type 数据库字段类型
+     * @return String
+     */
+    static String cloToJava(String type) {
+        Configuration config = getConfig();
+        assert config != null;
+        return config.getString(type, "unknowType");
+    }
+
+    /**
+     * 获取配置信息
+     */
+    public static PropertiesConfiguration getConfig() {
+        try {
+            return new PropertiesConfiguration("generator.properties");
+        } catch (ConfigurationException e) {
+            log.error(e.getMessage(), e);
+        }
+        return null;
+    }
+}

+ 421 - 0
eladmin-generator/src/main/java/me/zhengjie/utils/GenUtil.java

@@ -0,0 +1,421 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.utils;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.template.*;
+import lombok.extern.slf4j.Slf4j;
+import me.zhengjie.domain.GenConfig;
+import me.zhengjie.domain.ColumnInfo;
+import org.springframework.util.ObjectUtils;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.time.LocalDate;
+import java.util.*;
+
+import static me.zhengjie.utils.FileUtil.SYS_TEM_DIR;
+
+/**
+ * 代码生成
+ *
+ * @author Zheng Jie
+ * @date 2019-01-02
+ */
+@Slf4j
+@SuppressWarnings({"unchecked", "all"})
+public class GenUtil {
+
+    private static final String TIMESTAMP = "Timestamp";
+
+    private static final String BIGDECIMAL = "BigDecimal";
+
+    public static final String PK = "PRI";
+
+    public static final String EXTRA = "auto_increment";
+
+    /**
+     * 获取后端代码模板名称
+     *
+     * @return List
+     */
+    private static List<String> getAdminTemplateNames() {
+        List<String> templateNames = new ArrayList<>();
+        templateNames.add("Entity");
+        templateNames.add("Dto");
+        templateNames.add("Mapper");
+        templateNames.add("Controller");
+        templateNames.add("QueryCriteria");
+        templateNames.add("Service");
+        templateNames.add("ServiceImpl");
+        templateNames.add("Repository");
+        return templateNames;
+    }
+
+    /**
+     * 获取前端代码模板名称
+     *
+     * @return List
+     */
+    private static List<String> getFrontTemplateNames() {
+        List<String> templateNames = new ArrayList<>();
+        templateNames.add("index");
+        templateNames.add("api");
+        return templateNames;
+    }
+
+    public static List<Map<String, Object>> preview(List<ColumnInfo> columns, GenConfig genConfig) {
+        Map<String, Object> genMap = getGenMap(columns, genConfig);
+        List<Map<String, Object>> genList = new ArrayList<>();
+        // 获取后端模版
+        List<String> templates = getAdminTemplateNames();
+        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
+        for (String templateName : templates) {
+            Map<String, Object> map = new HashMap<>(1);
+            Template template = engine.getTemplate("generator/admin/" + templateName + ".ftl");
+            map.put("content", template.render(genMap));
+            map.put("name", templateName);
+            genList.add(map);
+        }
+        // 获取前端模版
+        templates = getFrontTemplateNames();
+        for (String templateName : templates) {
+            Map<String, Object> map = new HashMap<>(1);
+            Template template = engine.getTemplate("generator/front/" + templateName + ".ftl");
+            map.put(templateName, template.render(genMap));
+            map.put("content", template.render(genMap));
+            map.put("name", templateName);
+            genList.add(map);
+        }
+        return genList;
+    }
+
+    public static String download(List<ColumnInfo> columns, GenConfig genConfig) throws IOException {
+        // 拼接的路径:/tmpeladmin-gen-temp/,这个路径在Linux下需要root用户才有权限创建,非root用户会权限错误而失败,更改为: /tmp/eladmin-gen-temp/
+        // String tempPath =SYS_TEM_DIR + "eladmin-gen-temp" + File.separator + genConfig.getTableName() + File.separator;
+        String tempPath = SYS_TEM_DIR + "eladmin-gen-temp" + File.separator + genConfig.getTableName() + File.separator;
+        Map<String, Object> genMap = getGenMap(columns, genConfig);
+        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
+        // 生成后端代码
+        List<String> templates = getAdminTemplateNames();
+        for (String templateName : templates) {
+            Template template = engine.getTemplate("generator/admin/" + templateName + ".ftl");
+            String filePath = getAdminFilePath(templateName, genConfig, genMap.get("className").toString(), tempPath + "eladmin" + File.separator);
+            assert filePath != null;
+            File file = new File(filePath);
+            // 如果非覆盖生成
+            if (!genConfig.getCover() && FileUtil.exist(file)) {
+                continue;
+            }
+            // 生成代码
+            genFile(file, template, genMap);
+        }
+        // 生成前端代码
+        templates = getFrontTemplateNames();
+        for (String templateName : templates) {
+            Template template = engine.getTemplate("generator/front/" + templateName + ".ftl");
+            String path = tempPath + "eladmin-web" + File.separator;
+            String apiPath = path + "src" + File.separator + "api" + File.separator;
+            String srcPath = path + "src" + File.separator + "views" + File.separator + genMap.get("changeClassName").toString() + File.separator;
+            String filePath = getFrontFilePath(templateName, apiPath, srcPath, genMap.get("changeClassName").toString());
+            assert filePath != null;
+            File file = new File(filePath);
+            // 如果非覆盖生成
+            if (!genConfig.getCover() && FileUtil.exist(file)) {
+                continue;
+            }
+            // 生成代码
+            genFile(file, template, genMap);
+        }
+        return tempPath;
+    }
+
+    public static void generatorCode(List<ColumnInfo> columnInfos, GenConfig genConfig) throws IOException {
+        Map<String, Object> genMap = getGenMap(columnInfos, genConfig);
+        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
+        // 生成后端代码
+        List<String> templates = getAdminTemplateNames();
+        for (String templateName : templates) {
+            Template template = engine.getTemplate("generator/admin/" + templateName + ".ftl");
+            String rootPath = System.getProperty("user.dir");
+            String filePath = getAdminFilePath(templateName, genConfig, genMap.get("className").toString(), rootPath);
+
+            assert filePath != null;
+            File file = new File(filePath);
+
+            // 如果非覆盖生成
+            if (!genConfig.getCover() && FileUtil.exist(file)) {
+                continue;
+            }
+            // 生成代码
+            genFile(file, template, genMap);
+        }
+
+        // 生成前端代码
+        templates = getFrontTemplateNames();
+        for (String templateName : templates) {
+            Template template = engine.getTemplate("generator/front/" + templateName + ".ftl");
+            String filePath = getFrontFilePath(templateName, genConfig.getApiPath(), genConfig.getPath(), genMap.get("changeClassName").toString());
+
+            assert filePath != null;
+            File file = new File(filePath);
+
+            // 如果非覆盖生成
+            if (!genConfig.getCover() && FileUtil.exist(file)) {
+                continue;
+            }
+            // 生成代码
+            genFile(file, template, genMap);
+        }
+    }
+
+    // 获取模版数据
+    private static Map<String, Object> getGenMap(List<ColumnInfo> columnInfos, GenConfig genConfig) {
+        // 存储模版字段数据
+        Map<String, Object> genMap = new HashMap<>(16);
+        // 接口别名
+        genMap.put("apiAlias", genConfig.getApiAlias());
+        // 包名称
+        genMap.put("package", genConfig.getPack());
+        // 模块名称
+        genMap.put("moduleName", genConfig.getModuleName());
+        // 作者
+        genMap.put("author", genConfig.getAuthor());
+        // 创建日期
+        genMap.put("date", LocalDate.now().toString());
+        // 表名
+        genMap.put("tableName", genConfig.getTableName());
+        // 大写开头的类名
+        String className = StringUtils.toCapitalizeCamelCase(genConfig.getTableName());
+        // 小写开头的类名
+        String changeClassName = StringUtils.toCamelCase(genConfig.getTableName());
+        // 判断是否去除表前缀
+        if (StringUtils.isNotEmpty(genConfig.getPrefix())) {
+            className = StringUtils.toCapitalizeCamelCase(StrUtil.removePrefix(genConfig.getTableName(), genConfig.getPrefix()));
+            changeClassName = StringUtils.toCamelCase(StrUtil.removePrefix(genConfig.getTableName(), genConfig.getPrefix()));
+            changeClassName = StringUtils.uncapitalize(changeClassName);
+        }
+        // 保存类名
+        genMap.put("className", className);
+        // 保存小写开头的类名
+        genMap.put("changeClassName", changeClassName);
+        // 存在 Timestamp 字段
+        genMap.put("hasTimestamp", false);
+        // 查询类中存在 Timestamp 字段
+        genMap.put("queryHasTimestamp", false);
+        // 存在 BigDecimal 字段
+        genMap.put("hasBigDecimal", false);
+        // 查询类中存在 BigDecimal 字段
+        genMap.put("queryHasBigDecimal", false);
+        // 是否需要创建查询
+        genMap.put("hasQuery", false);
+        // 自增主键
+        genMap.put("auto", false);
+        // 存在字典
+        genMap.put("hasDict", false);
+        // 存在日期注解
+        genMap.put("hasDateAnnotation", false);
+        // 保存字段信息
+        List<Map<String, Object>> columns = new ArrayList<>();
+        // 保存查询字段的信息
+        List<Map<String, Object>> queryColumns = new ArrayList<>();
+        // 存储字典信息
+        List<String> dicts = new ArrayList<>();
+        // 存储 between 信息
+        List<Map<String, Object>> betweens = new ArrayList<>();
+        // 存储不为空的字段信息
+        List<Map<String, Object>> isNotNullColumns = new ArrayList<>();
+
+        for (ColumnInfo column : columnInfos) {
+            Map<String, Object> listMap = new HashMap<>(16);
+            // 字段描述
+            listMap.put("remark", column.getRemark());
+            // 字段类型
+            listMap.put("columnKey", column.getKeyType());
+            // 主键类型
+            String colType = ColUtil.cloToJava(column.getColumnType());
+            // 小写开头的字段名
+            String changeColumnName = StringUtils.toCamelCase(column.getColumnName());
+            // 大写开头的字段名
+            String capitalColumnName = StringUtils.toCapitalizeCamelCase(column.getColumnName());
+            if (PK.equals(column.getKeyType())) {
+                // 存储主键类型
+                genMap.put("pkColumnType", colType);
+                // 存储小写开头的字段名
+                genMap.put("pkChangeColName", changeColumnName);
+                // 存储大写开头的字段名
+                genMap.put("pkCapitalColName", capitalColumnName);
+            }
+            // 是否存在 Timestamp 类型的字段
+            if (TIMESTAMP.equals(colType)) {
+                genMap.put("hasTimestamp", true);
+            }
+            // 是否存在 BigDecimal 类型的字段
+            if (BIGDECIMAL.equals(colType)) {
+                genMap.put("hasBigDecimal", true);
+            }
+            // 主键是否自增
+            if (EXTRA.equals(column.getExtra())) {
+                genMap.put("auto", true);
+            }
+            // 主键存在字典
+            if (StringUtils.isNotBlank(column.getDictName())) {
+                genMap.put("hasDict", true);
+                dicts.add(column.getDictName());
+            }
+
+            // 存储字段类型
+            listMap.put("columnType", colType);
+            // 存储字原始段名称
+            listMap.put("columnName", column.getColumnName());
+            // 不为空
+            listMap.put("istNotNull", column.getNotNull());
+            // 字段列表显示
+            listMap.put("columnShow", column.getListShow());
+            // 表单显示
+            listMap.put("formShow", column.getFormShow());
+            // 表单组件类型
+            listMap.put("formType", StringUtils.isNotBlank(column.getFormType()) ? column.getFormType() : "Input");
+            // 小写开头的字段名称
+            listMap.put("changeColumnName", changeColumnName);
+            //大写开头的字段名称
+            listMap.put("capitalColumnName", capitalColumnName);
+            // 字典名称
+            listMap.put("dictName", column.getDictName());
+            // 日期注解
+            listMap.put("dateAnnotation", column.getDateAnnotation());
+            if (StringUtils.isNotBlank(column.getDateAnnotation())) {
+                genMap.put("hasDateAnnotation", true);
+            }
+            // 添加非空字段信息
+            if (column.getNotNull()) {
+                isNotNullColumns.add(listMap);
+            }
+            // 判断是否有查询,如有则把查询的字段set进columnQuery
+            if (!StringUtils.isBlank(column.getQueryType())) {
+                // 查询类型
+                listMap.put("queryType", column.getQueryType());
+                // 是否存在查询
+                genMap.put("hasQuery", true);
+                if (TIMESTAMP.equals(colType)) {
+                    // 查询中存储 Timestamp 类型
+                    genMap.put("queryHasTimestamp", true);
+                }
+                if (BIGDECIMAL.equals(colType)) {
+                    // 查询中存储 BigDecimal 类型
+                    genMap.put("queryHasBigDecimal", true);
+                }
+                if ("between".equalsIgnoreCase(column.getQueryType())) {
+                    betweens.add(listMap);
+                } else {
+                    // 添加到查询列表中
+                    queryColumns.add(listMap);
+                }
+            }
+            // 添加到字段列表中
+            columns.add(listMap);
+        }
+        // 保存字段列表
+        genMap.put("columns", columns);
+        // 保存查询列表
+        genMap.put("queryColumns", queryColumns);
+        // 保存字段列表
+        genMap.put("dicts", dicts);
+        // 保存查询列表
+        genMap.put("betweens", betweens);
+        // 保存非空字段信息
+        genMap.put("isNotNullColumns", isNotNullColumns);
+        return genMap;
+    }
+
+    /**
+     * 定义后端文件路径以及名称
+     */
+    private static String getAdminFilePath(String templateName, GenConfig genConfig, String className, String rootPath) {
+        String projectPath = rootPath + File.separator + genConfig.getModuleName();
+        String packagePath = projectPath + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator;
+        if (!ObjectUtils.isEmpty(genConfig.getPack())) {
+            packagePath += genConfig.getPack().replace(".", File.separator) + File.separator;
+        }
+
+        if ("Entity".equals(templateName)) {
+            return packagePath + "domain" + File.separator + className + ".java";
+        }
+
+        if ("Controller".equals(templateName)) {
+            return packagePath + "rest" + File.separator + className + "Controller.java";
+        }
+
+        if ("Service".equals(templateName)) {
+            return packagePath + "service" + File.separator + className + "Service.java";
+        }
+
+        if ("ServiceImpl".equals(templateName)) {
+            return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
+        }
+
+        if ("Dto".equals(templateName)) {
+            return packagePath + "service" + File.separator + "dto" + File.separator + className + "Dto.java";
+        }
+
+        if ("QueryCriteria".equals(templateName)) {
+            return packagePath + "service" + File.separator + "dto" + File.separator + className + "QueryCriteria.java";
+        }
+
+        if ("Mapper".equals(templateName)) {
+            return packagePath + "service" + File.separator + "mapstruct" + File.separator + className + "Mapper.java";
+        }
+
+        if ("Repository".equals(templateName)) {
+            return packagePath + "repository" + File.separator + className + "Repository.java";
+        }
+
+        return null;
+    }
+
+    /**
+     * 定义前端文件路径以及名称
+     */
+    private static String getFrontFilePath(String templateName, String apiPath, String path, String apiName) {
+
+        if ("api".equals(templateName)) {
+            return apiPath + File.separator + apiName + ".js";
+        }
+
+        if ("index".equals(templateName)) {
+            return path + File.separator + "index.vue";
+        }
+
+        return null;
+    }
+
+    private static void genFile(File file, Template template, Map<String, Object> map) throws IOException {
+        // 生成目标文件
+        Writer writer = null;
+        try {
+            FileUtil.touch(file);
+            writer = new FileWriter(file);
+            template.render(map, writer);
+        } catch (TemplateException | IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            assert writer != null;
+            writer.close();
+        }
+    }
+}

+ 22 - 0
eladmin-logging/pom.xml

@@ -0,0 +1,22 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>eladmin</artifactId>
+        <groupId>me.zhengjie</groupId>
+        <version>2.6</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>eladmin-logging</artifactId>
+    <name>日志模块</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>me.zhengjie</groupId>
+            <artifactId>eladmin-common</artifactId>
+            <version>2.6</version>
+        </dependency>
+    </dependencies>
+</project>

+ 31 - 0
eladmin-logging/src/main/java/me/zhengjie/annotation/Log.java

@@ -0,0 +1,31 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-24
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Log {
+    String value() default "";
+}

+ 98 - 0
eladmin-logging/src/main/java/me/zhengjie/aspect/LogAspect.java

@@ -0,0 +1,98 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.aspect;
+
+import lombok.extern.slf4j.Slf4j;
+import me.zhengjie.domain.Log;
+import me.zhengjie.service.LogService;
+import me.zhengjie.utils.RequestHolder;
+import me.zhengjie.utils.SecurityUtils;
+import me.zhengjie.utils.StringUtils;
+import me.zhengjie.utils.ThrowableUtil;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.stereotype.Component;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-24
+ */
+@Component
+@Aspect
+@Slf4j
+public class LogAspect {
+
+    private final LogService logService;
+
+    ThreadLocal<Long> currentTime = new ThreadLocal<>();
+
+    public LogAspect(LogService logService) {
+        this.logService = logService;
+    }
+
+    /**
+     * 配置切入点
+     */
+    @Pointcut("@annotation(me.zhengjie.annotation.Log)")
+    public void logPointcut() {
+        // 该方法无方法体,主要为了让同类中其他方法使用此切入点
+    }
+
+    /**
+     * 配置环绕通知,使用在方法logPointcut()上注册的切入点
+     *
+     * @param joinPoint join point for advice
+     */
+    @Around("logPointcut()")
+    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
+        Object result;
+        currentTime.set(System.currentTimeMillis());
+        result = joinPoint.proceed();
+        Log log = new Log("INFO",System.currentTimeMillis() - currentTime.get());
+        currentTime.remove();
+        HttpServletRequest request = RequestHolder.getHttpServletRequest();
+        logService.save(getUsername(), StringUtils.getBrowser(request), StringUtils.getIp(request),joinPoint, log);
+        return result;
+    }
+
+    /**
+     * 配置异常通知
+     *
+     * @param joinPoint join point for advice
+     * @param e exception
+     */
+    @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
+    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
+        Log log = new Log("ERROR",System.currentTimeMillis() - currentTime.get());
+        currentTime.remove();
+        log.setExceptionDetail(ThrowableUtil.getStackTrace(e).getBytes());
+        HttpServletRequest request = RequestHolder.getHttpServletRequest();
+        logService.save(getUsername(), StringUtils.getBrowser(request), StringUtils.getIp(request), (ProceedingJoinPoint)joinPoint, log);
+    }
+
+    public String getUsername() {
+        try {
+            return SecurityUtils.getCurrentUsername();
+        }catch (Exception e){
+            return "";
+        }
+    }
+}

+ 154 - 0
eladmin-logging/src/main/java/me/zhengjie/aspect/LogRequestParam.java

@@ -0,0 +1,154 @@
+package me.zhengjie.aspect;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import lombok.extern.slf4j.Slf4j;
+import me.zhengjie.utils.IpAddressUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.CodeSignature;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.multipart.MultipartFile;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.util.Objects;
+
+@Slf4j
+@Aspect
+@Component
+public class LogRequestParam {
+
+	@Pointcut("execution(* me.zhengjie.application.*.controller..*.*(..))")
+	public void pointCut() {
+	}
+
+	public static boolean isJSON(String str) {
+		boolean result = false;
+		if (StringUtils.isNotBlank(str)) {
+			str = str.trim();
+			if (str.startsWith("{") && str.endsWith("}")) {
+				result = true;
+			} else if (str.startsWith("[") && str.endsWith("]")) {
+				result = true;
+			}
+		}
+		return result;
+	}
+
+	@Before(value = "pointCut()")
+	public void doBefore(JoinPoint joinPoint) {
+		ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder
+				.getRequestAttributes();
+		if (Objects.isNull(servletRequestAttributes)) {
+			return;
+		}
+		HttpServletRequest request = servletRequestAttributes.getRequest();
+		// 得到请求的ip地址
+		String ip = IpAddressUtil.getIpAddress(request);
+
+		String uri = request.getRequestURI();
+		// 请求方法
+		String method = request.getMethod();
+		Object error = "";
+		try {
+			Object[] parameterValues = joinPoint.getArgs();
+			int parameterValuesLength = parameterValues.length;
+			if (parameterValuesLength == 0) {
+				log.debug("[请求参数] - 无");
+				return;
+			}
+			Signature signature = joinPoint.getSignature();
+			if (Objects.isNull(signature)) {
+				return;
+			}
+			String[] parameterNames = ((CodeSignature) signature).getParameterNames();
+			if (Objects.isNull(parameterNames) || parameterNames.length != parameterValuesLength) {
+				return;
+			}
+			String paramJson = "";
+			for (int i = 0; i < parameterNames.length; i++) {
+				Object value = parameterValues[i];
+				if (value == null || value instanceof HttpServletRequest || value instanceof HttpServletResponse) {
+					continue;
+				} else if (value instanceof MultipartFile) {
+					MultipartFile part = (MultipartFile) value;
+					paramJson = part.getOriginalFilename();
+				} else if (value instanceof MultipartFile[]) {
+					MultipartFile[] part = (MultipartFile[]) value;
+					StringBuffer sb = new StringBuffer();
+					for (MultipartFile file : part) {
+						sb.append(file.getOriginalFilename() + ",");
+					}
+					paramJson = sb.toString();
+				} else if (value instanceof String) {
+					paramJson = (String) value;
+					if (paramJson.length() > 1000) {
+						// 去掉特别长的参数
+						paramJson = paramJson.substring(0, 1000);
+					}
+				} else {
+					// 处理参数是对象的值,如果出现异常,说明有文件流
+					error = value;
+					paramJson = JSON.toJSONString(value);
+					// 先进行赋值
+					// value = object;
+					if (StringUtils.isNotBlank(paramJson) && paramJson.length() > 1000) {
+						StringBuilder sb = new StringBuilder();
+						// valLength = fieldValue.substring(0, 1000);
+						sb.append(paramJson.substring(0, 1000));
+						sb.append(paramJson.substring(paramJson.length() - 1000));
+						paramJson = sb.toString();
+					}
+				}
+			}
+
+			// 这里判断
+			Object[] obj = { ip, method, uri, paramJson };
+			log.info("[request] - ip: {} , method: {} , requestUrl: {} , param: {} ", obj);
+		} catch (Exception e) {
+			// e.printStackTrace();
+			// 这里判断
+			Object[] obj = { ip, method, uri, error.toString() };
+			log.info("[request] - ip: {} , method: {} , requestUrl: {} , param: {}, ", obj);
+
+		}
+
+	}
+
+	@AfterReturning(returning = "result", pointcut = "pointCut() ")
+	public void doAfterReturning(Object result) {
+		try {
+			ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder
+					.getRequestAttributes();
+			if (Objects.isNull(servletRequestAttributes)) {
+				return;
+			}
+			HttpServletResponse response = servletRequestAttributes.getResponse();
+			if (Objects.isNull(response)) {
+				return;
+			}
+			int status = response.getStatus();
+			HttpServletRequest request = servletRequestAttributes.getRequest();
+			String json = JSON.toJSONString(result, SerializerFeature.WriteMapNullValue);
+			// 打印输出的值
+			if (StringUtils.isNotBlank(json) && json.length() < 1500) {
+				String uri = request.getRequestURI();
+				String method = request.getMethod();
+				Object obj[] = { status, method, uri, json };
+				log.info("[reponse] - status: {} ,method:{}, url:{} ,result:{}", obj);
+			}
+
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+}

+ 80 - 0
eladmin-logging/src/main/java/me/zhengjie/domain/Log.java

@@ -0,0 +1,80 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.domain;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.annotations.CreationTimestamp;
+import javax.persistence.*;
+import java.io.Serializable;
+import java.sql.Timestamp;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-24
+ */
+@Entity
+@Getter
+@Setter
+@Table(name = "sys_log")
+@NoArgsConstructor
+public class Log  implements Serializable {
+
+    @Id
+    @Column(name = "log_id")
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    /** 操作用户 */
+    private String username;
+
+    /** 描述 */
+    private String description;
+
+    /** 方法名 */
+    private String method;
+
+    /** 参数 */
+    private String params;
+
+    /** 日志类型 */
+    private String logType;
+
+    /** 请求ip */
+    private String requestIp;
+
+    /** 地址 */
+    private String address;
+
+    /** 浏览器  */
+    private String browser;
+
+    /** 请求耗时 */
+    private Long time;
+
+    /** 异常详细  */
+    private byte[] exceptionDetail;
+
+    /** 创建日期 */
+    @CreationTimestamp
+    private Timestamp createTime;
+
+    public Log(String logType, Long time) {
+        this.logType = logType;
+        this.time = time;
+    }
+}

+ 39 - 0
eladmin-logging/src/main/java/me/zhengjie/repository/LogRepository.java

@@ -0,0 +1,39 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.repository;
+
+import me.zhengjie.domain.Log;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-24
+ */
+@Repository
+public interface LogRepository extends JpaRepository<Log,Long>, JpaSpecificationExecutor<Log> {
+
+    /**
+     * 根据日志类型删除信息
+     * @param logType 日志类型
+     */
+    @Modifying
+    @Query(value = "delete from sys_log where log_type = ?1", nativeQuery = true)
+    void deleteByLogType(String logType);
+}

+ 109 - 0
eladmin-logging/src/main/java/me/zhengjie/rest/LogController.java

@@ -0,0 +1,109 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.rest;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import me.zhengjie.annotation.Log;
+import me.zhengjie.service.LogService;
+import me.zhengjie.service.dto.LogQueryCriteria;
+import me.zhengjie.utils.SecurityUtils;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-24
+ */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/logs")
+@Api(tags = "系统:日志管理")
+public class LogController {
+
+    private final LogService logService;
+
+    @Log("导出数据")
+    @ApiOperation("导出数据")
+    @GetMapping(value = "/download")
+    @PreAuthorize("@el.check()")
+    public void exportLog(HttpServletResponse response, LogQueryCriteria criteria) throws IOException {
+        criteria.setLogType("INFO");
+        logService.download(logService.queryAll(criteria), response);
+    }
+
+    @Log("导出错误数据")
+    @ApiOperation("导出错误数据")
+    @GetMapping(value = "/error/download")
+    @PreAuthorize("@el.check()")
+    public void exportErrorLog(HttpServletResponse response, LogQueryCriteria criteria) throws IOException {
+        criteria.setLogType("ERROR");
+        logService.download(logService.queryAll(criteria), response);
+    }
+    @GetMapping
+    @ApiOperation("日志查询")
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<Object> queryLog(LogQueryCriteria criteria, Pageable pageable){
+        criteria.setLogType("INFO");
+        return new ResponseEntity<>(logService.queryAll(criteria,pageable), HttpStatus.OK);
+    }
+
+    @GetMapping(value = "/user")
+    @ApiOperation("用户日志查询")
+    public ResponseEntity<Object> queryUserLog(LogQueryCriteria criteria, Pageable pageable){
+        criteria.setLogType("INFO");
+        criteria.setBlurry(SecurityUtils.getCurrentUsername());
+        return new ResponseEntity<>(logService.queryAllByUser(criteria,pageable), HttpStatus.OK);
+    }
+
+    @GetMapping(value = "/error")
+    @ApiOperation("错误日志查询")
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<Object> queryErrorLog(LogQueryCriteria criteria, Pageable pageable){
+        criteria.setLogType("ERROR");
+        return new ResponseEntity<>(logService.queryAll(criteria,pageable), HttpStatus.OK);
+    }
+
+    @GetMapping(value = "/error/{id}")
+    @ApiOperation("日志异常详情查询")
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<Object> queryErrorLogDetail(@PathVariable Long id){
+        return new ResponseEntity<>(logService.findByErrDetail(id), HttpStatus.OK);
+    }
+    @DeleteMapping(value = "/del/error")
+    @Log("删除所有ERROR日志")
+    @ApiOperation("删除所有ERROR日志")
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<Object> delAllErrorLog(){
+        logService.delAllByError();
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @DeleteMapping(value = "/del/info")
+    @Log("删除所有INFO日志")
+    @ApiOperation("删除所有INFO日志")
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<Object> delAllInfoLog(){
+        logService.delAllByInfo();
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}

+ 92 - 0
eladmin-logging/src/main/java/me/zhengjie/service/LogService.java

@@ -0,0 +1,92 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.service;
+
+import me.zhengjie.domain.Log;
+import me.zhengjie.service.dto.LogQueryCriteria;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.springframework.data.domain.Pageable;
+import org.springframework.scheduling.annotation.Async;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-24
+ */
+public interface LogService {
+
+    /**
+     * 分页查询
+     * @param criteria 查询条件
+     * @param pageable 分页参数
+     * @return /
+     */
+    Object queryAll(LogQueryCriteria criteria, Pageable pageable);
+
+    /**
+     * 查询全部数据
+     * @param criteria 查询条件
+     * @return /
+     */
+    List<Log> queryAll(LogQueryCriteria criteria);
+
+    /**
+     * 查询用户日志
+     * @param criteria 查询条件
+     * @param pageable 分页参数
+     * @return -
+     */
+    Object queryAllByUser(LogQueryCriteria criteria, Pageable pageable);
+
+    /**
+     * 保存日志数据
+     * @param username 用户
+     * @param browser 浏览器
+     * @param ip 请求IP
+     * @param joinPoint /
+     * @param log 日志实体
+     */
+    @Async
+    void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, Log log);
+
+    /**
+     * 查询异常详情
+     * @param id 日志ID
+     * @return Object
+     */
+    Object findByErrDetail(Long id);
+
+    /**
+     * 导出日志
+     * @param logs 待导出的数据
+     * @param response /
+     * @throws IOException /
+     */
+    void download(List<Log> logs, HttpServletResponse response) throws IOException;
+
+    /**
+     * 删除所有错误日志
+     */
+    void delAllByError();
+
+    /**
+     * 删除所有INFO日志
+     */
+    void delAllByInfo();
+}

+ 46 - 0
eladmin-logging/src/main/java/me/zhengjie/service/dto/LogErrorDTO.java

@@ -0,0 +1,46 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.service.dto;
+
+import lombok.Data;
+import java.io.Serializable;
+import java.sql.Timestamp;
+
+/**
+* @author Zheng Jie
+* @date 2019-5-22
+*/
+@Data
+public class LogErrorDTO implements Serializable {
+
+    private Long id;
+
+    private String username;
+
+    private String description;
+
+    private String method;
+
+    private String params;
+
+    private String browser;
+
+    private String requestIp;
+
+    private String address;
+
+    private Timestamp createTime;
+}

+ 39 - 0
eladmin-logging/src/main/java/me/zhengjie/service/dto/LogQueryCriteria.java

@@ -0,0 +1,39 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.service.dto;
+
+import lombok.Data;
+import me.zhengjie.annotation.Query;
+import java.sql.Timestamp;
+import java.util.List;
+
+/**
+ * 日志查询类
+ * @author Zheng Jie
+ * @date 2019-6-4 09:23:07
+ */
+@Data
+public class LogQueryCriteria {
+
+    @Query(blurry = "username,description,address,requestIp,method,params")
+    private String blurry;
+
+    @Query
+    private String logType;
+
+    @Query(type = Query.Type.BETWEEN)
+    private List<Timestamp> createTime;
+}

+ 40 - 0
eladmin-logging/src/main/java/me/zhengjie/service/dto/LogSmallDTO.java

@@ -0,0 +1,40 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.service.dto;
+
+import lombok.Data;
+import java.io.Serializable;
+import java.sql.Timestamp;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-5-22
+ */
+@Data
+public class LogSmallDTO implements Serializable {
+
+    private String description;
+
+    private String requestIp;
+
+    private Long time;
+
+    private String address;
+
+    private String browser;
+
+    private Timestamp createTime;
+}

+ 168 - 0
eladmin-logging/src/main/java/me/zhengjie/service/impl/LogServiceImpl.java

@@ -0,0 +1,168 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.service.impl;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
+import lombok.RequiredArgsConstructor;
+import me.zhengjie.domain.Log;
+import me.zhengjie.repository.LogRepository;
+import me.zhengjie.service.LogService;
+import me.zhengjie.service.dto.LogQueryCriteria;
+import me.zhengjie.service.mapstruct.LogErrorMapper;
+import me.zhengjie.service.mapstruct.LogSmallMapper;
+import me.zhengjie.utils.*;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.*;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-24
+ */
+@Service
+@RequiredArgsConstructor
+public class LogServiceImpl implements LogService {
+    private final LogRepository logRepository;
+    private final LogErrorMapper logErrorMapper;
+    private final LogSmallMapper logSmallMapper;
+
+    @Override
+    public Object queryAll(LogQueryCriteria criteria, Pageable pageable) {
+        Page<Log> page = logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)), pageable);
+        String status = "ERROR";
+        if (status.equals(criteria.getLogType())) {
+            return PageUtil.toPage(page.map(logErrorMapper::toDto));
+        }
+        return page;
+    }
+
+    @Override
+    public List<Log> queryAll(LogQueryCriteria criteria) {
+        return logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)));
+    }
+
+    @Override
+    public Object queryAllByUser(LogQueryCriteria criteria, Pageable pageable) {
+        Page<Log> page = logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)), pageable);
+        return PageUtil.toPage(page.map(logSmallMapper::toDto));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, Log log) {
+        if (log == null) {
+            throw new IllegalArgumentException("Log 不能为 null!");
+        }
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+        me.zhengjie.annotation.Log aopLog = method.getAnnotation(me.zhengjie.annotation.Log.class);
+
+        // 方法路径
+        String methodName = joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()";
+
+        // 描述
+        log.setDescription(aopLog.value());
+        
+        log.setRequestIp(ip);
+        log.setAddress(StringUtils.getCityInfo(log.getRequestIp()));
+        log.setMethod(methodName);
+        log.setUsername(username);
+        log.setParams(getParameter(method, joinPoint.getArgs()));
+        log.setBrowser(browser);
+        logRepository.save(log);
+    }
+
+    /**
+     * 根据方法和传入的参数获取请求参数
+     */
+    private String getParameter(Method method, Object[] args) {
+        List<Object> argList = new ArrayList<>();
+        Parameter[] parameters = method.getParameters();
+        for (int i = 0; i < parameters.length; i++) {
+            //将RequestBody注解修饰的参数作为请求参数
+            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
+            if (requestBody != null) {
+                argList.add(args[i]);
+            }
+            //将RequestParam注解修饰的参数作为请求参数
+            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
+            if (requestParam != null) {
+                Map<String, Object> map = new HashMap<>(4);
+                String key = parameters[i].getName();
+                if (!StringUtils.isEmpty(requestParam.value())) {
+                    key = requestParam.value();
+                }
+                map.put(key, args[i]);
+                argList.add(map);
+            }
+        }
+        if (argList.isEmpty()) {
+            return "";
+        }
+        return argList.size() == 1 ? JSONUtil.toJsonStr(argList.get(0)) : JSONUtil.toJsonStr(argList);
+    }
+
+    @Override
+    public Object findByErrDetail(Long id) {
+        Log log = logRepository.findById(id).orElseGet(Log::new);
+        ValidationUtil.isNull(log.getId(), "Log", "id", id);
+        byte[] details = log.getExceptionDetail();
+        return Dict.create().set("exception", new String(ObjectUtil.isNotNull(details) ? details : "".getBytes()));
+    }
+
+    @Override
+    public void download(List<Log> logs, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (Log log : logs) {
+            Map<String, Object> map = new LinkedHashMap<>();
+            map.put("用户名", log.getUsername());
+            map.put("IP", log.getRequestIp());
+            map.put("IP来源", log.getAddress());
+            map.put("描述", log.getDescription());
+            map.put("浏览器", log.getBrowser());
+            map.put("请求耗时/毫秒", log.getTime());
+            map.put("异常详情", new String(ObjectUtil.isNotNull(log.getExceptionDetail()) ? log.getExceptionDetail() : "".getBytes()));
+            map.put("创建日期", log.getCreateTime());
+            list.add(map);
+        }
+        FileUtil.downloadExcel(list, response);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delAllByError() {
+        logRepository.deleteByLogType("ERROR");
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delAllByInfo() {
+        logRepository.deleteByLogType("INFO");
+    }
+}

+ 31 - 0
eladmin-logging/src/main/java/me/zhengjie/service/mapstruct/LogErrorMapper.java

@@ -0,0 +1,31 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.service.mapstruct;
+
+import me.zhengjie.base.BaseMapper;
+import me.zhengjie.domain.Log;
+import me.zhengjie.service.dto.LogErrorDTO;
+import org.mapstruct.Mapper;
+import org.mapstruct.ReportingPolicy;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-5-22
+ */
+@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE)
+public interface LogErrorMapper extends BaseMapper<LogErrorDTO, Log> {
+
+}

+ 31 - 0
eladmin-logging/src/main/java/me/zhengjie/service/mapstruct/LogSmallMapper.java

@@ -0,0 +1,31 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.service.mapstruct;
+
+import me.zhengjie.base.BaseMapper;
+import me.zhengjie.domain.Log;
+import me.zhengjie.service.dto.LogSmallDTO;
+import org.mapstruct.Mapper;
+import org.mapstruct.ReportingPolicy;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-5-22
+ */
+@Mapper(componentModel = "spring",unmappedTargetPolicy = ReportingPolicy.IGNORE)
+public interface LogSmallMapper extends BaseMapper<LogSmallDTO, Log> {
+
+}

+ 36 - 0
eladmin-mnt/pom.xml

@@ -0,0 +1,36 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>eladmin</artifactId>
+        <groupId>me.zhengjie</groupId>
+        <version>2.6</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>eladmin-mnt</artifactId>
+    <name>运维模块</name>
+
+    <properties>
+
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>me.zhengjie</groupId>
+            <artifactId>eladmin-logging</artifactId>
+            <version>2.6</version>
+        </dependency>
+        <!-- linux的管理 -->
+        <dependency>
+            <groupId>ch.ethz.ganymed</groupId>
+            <artifactId>ganymed-ssh2</artifactId>
+            <version>build210</version>
+        </dependency>
+        <dependency>
+            <groupId>com.jcraft</groupId>
+            <artifactId>jsch</artifactId>
+            <version>0.1.55</version>
+        </dependency>
+    </dependencies>
+</project>

+ 68 - 0
eladmin-mnt/src/main/java/me/zhengjie/mnt/domain/App.java

@@ -0,0 +1,68 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.mnt.domain;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.bean.copier.CopyOptions;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import me.zhengjie.base.BaseEntity;
+
+import javax.persistence.*;
+import java.io.Serializable;
+
+/**
+* @author zhanghouying
+* @date 2019-08-24
+*/
+@Entity
+@Getter
+@Setter
+@Table(name="mnt_app")
+public class App extends BaseEntity implements Serializable {
+
+    @Id
+	@Column(name = "app_id")
+	@ApiModelProperty(value = "ID", hidden = true)
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+	@ApiModelProperty(value = "名称")
+    private String name;
+
+	@ApiModelProperty(value = "端口")
+	private int port;
+
+	@ApiModelProperty(value = "上传路径")
+	private String uploadPath;
+
+	@ApiModelProperty(value = "部署路径")
+	private String deployPath;
+
+	@ApiModelProperty(value = "备份路径")
+	private String backupPath;
+
+	@ApiModelProperty(value = "启动脚本")
+	private String startScript;
+
+	@ApiModelProperty(value = "部署脚本")
+	private String deployScript;
+
+    public void copy(App source){
+        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
+    }
+}

+ 61 - 0
eladmin-mnt/src/main/java/me/zhengjie/mnt/domain/Database.java

@@ -0,0 +1,61 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.mnt.domain;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.bean.copier.CopyOptions;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import me.zhengjie.base.BaseEntity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+* @author zhanghouying
+* @date 2019-08-24
+*/
+@Entity
+@Getter
+@Setter
+@Table(name="mnt_database")
+public class Database extends BaseEntity implements Serializable {
+
+    @Id
+    @Column(name = "db_id")
+	@ApiModelProperty(value = "ID", hidden = true)
+    private String id;
+
+	@ApiModelProperty(value = "数据库名称")
+    private String name;
+
+	@ApiModelProperty(value = "数据库连接地址")
+    private String jdbcUrl;
+
+	@ApiModelProperty(value = "数据库密码")
+    private String pwd;
+
+	@ApiModelProperty(value = "用户名")
+    private String userName;
+
+    public void copy(Database source){
+        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
+    }
+}

+ 60 - 0
eladmin-mnt/src/main/java/me/zhengjie/mnt/domain/Deploy.java

@@ -0,0 +1,60 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package me.zhengjie.mnt.domain;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.bean.copier.CopyOptions;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import me.zhengjie.base.BaseEntity;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+* @author zhanghouying
+* @date 2019-08-24
+*/
+@Entity
+@Getter
+@Setter
+@Table(name="mnt_deploy")
+public class Deploy extends BaseEntity implements Serializable {
+
+    @Id
+	@Column(name = "deploy_id")
+	@ApiModelProperty(value = "ID", hidden = true)
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+	@ManyToMany
+	@ApiModelProperty(name = "服务器", hidden = true)
+	@JoinTable(name = "mnt_deploy_server",
+			joinColumns = {@JoinColumn(name = "deploy_id",referencedColumnName = "deploy_id")},
+			inverseJoinColumns = {@JoinColumn(name = "server_id",referencedColumnName = "server_id")})
+	private Set<ServerDeploy> deploys;
+
+	@ManyToOne
+    @JoinColumn(name = "app_id")
+	@ApiModelProperty(value = "应用编号")
+    private App app;
+
+    public void copy(Deploy source){
+        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
+    }
+}

+ 0 - 0
eladmin-mnt/src/main/java/me/zhengjie/mnt/domain/DeployHistory.java


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