From e718afd02965c6a4018506acb1ae99baca0c5645 Mon Sep 17 00:00:00 2001
From: xin <1099200748@qq.com>
Date: Tue, 15 Apr 2025 18:40:42 +0800
Subject: [PATCH] 1.0

---
 oying-common/src/main/java/com/oying/exception/handler/ApiError.java                            |   34 
 oying-common/src/main/java/com/oying/aspect/LimitType.java                                      |   12 
 oying-system/src/main/resources/mapper/system/MenuMapper.xml                                    |   79 
 .idea/dataSources.xml                                                                           |   18 
 oying-system/src/main/java/com/oying/AppRun.java                                                |   51 
 .idea/misc.xml                                                                                  |   12 
 oying-tools/src/main/java/com/oying/domain/dto/LocalStorageQueryCriteria.java                   |   26 
 oying-system/src/main/java/com/oying/modules/system/service/impl/DataServiceImpl.java           |   81 
 oying-system/src/main/java/com/oying/modules/security/service/dto/AuthUserDto.java              |   29 
 oying-system/src/main/resources/template/email.ftl                                              |   48 
 oying-system/src/main/java/com/oying/modules/system/mapper/MenuMapper.java                      |   39 
 oying-common/src/main/java/com/oying/annotation/Limit.java                                      |   34 
 oying-system/src/main/resources/mapper/system/DictMapper.xml                                    |   31 
 oying-common/src/main/java/com/oying/exception/EntityExistException.java                        |   19 
 oying-generator/src/main/java/com/oying/service/GenConfigService.java                           |   26 
 oying-system/src/main/java/com/oying/modules/system/domain/dto/MenuVo.java                      |   39 
 oying-common/src/main/java/com/oying/config/properties/RsaProperties.java                       |   22 
 oying-generator/src/main/java/com/oying/rest/GenConfigController.java                           |   36 
 oying-system/src/main/java/com/oying/modules/system/mapper/UserRoleMapper.java                  |   19 
 oying-tools/src/main/resources/mapper/LocalStorageMapper.xml                                    |   41 
 oying-common/src/main/java/com/oying/utils/PageResult.java                                      |   21 
 oying-common/src/main/java/com/oying/base/BaseEntity.java                                       |   65 
 oying-logging/src/main/java/com/oying/service/impl/SysLogServiceImpl.java                       |  169 
 oying-system/src/main/resources/spy.properties                                                  |   20 
 oying-system/src/main/java/com/oying/modules/system/rest/JobController.java                     |   81 
 oying-system/src/main/java/com/oying/modules/security/config/enums/LoginCodeEnum.java           |   31 
 oying-generator/src/main/java/com/oying/domain/ColumnInfo.java                                  |   62 
 pom.xml                                                                                         |  264 
 oying-system/src/main/java/com/oying/modules/system/mapper/DictMapper.java                      |   22 
 oying-common/src/main/java/com/oying/config/mybatis/MyMetaObjectHandler.java                    |   40 
 oying-system/src/main/java/com/oying/modules/system/domain/User.java                            |  105 
 oying-system/src/main/java/com/oying/modules/system/rest/MenuController.java                    |  138 
 oying-logging/src/main/java/com/oying/service/SysLogService.java                                |   77 
 oying-system/src/main/java/com/oying/modules/security/config/SecurityProperties.java            |   61 
 oying-common/src/main/java/com/oying/config/AsyncExecutor.java                                  |   80 
 oying-system/src/main/resources/mapper/system/DeptMapper.xml                                    |   70 
 oying-system/src/main/java/com/oying/modules/system/rest/MonitorController.java                 |   30 
 oying-system/src/main/java/com/oying/modules/system/domain/dto/DictQueryCriteria.java           |   21 
 oying-common/src/main/java/com/oying/utils/enums/DataScopeEnum.java                             |   38 
 oying-system/src/main/java/com/oying/modules/quartz/config/JobRunner.java                       |   36 
 oying-common/src/main/java/com/oying/config/webConfig/ConfigurerAdapter.java                    |   82 
 oying-system/src/main/java/com/oying/modules/system/service/JobService.java                     |   74 
 oying-system/src/main/java/com/oying/modules/system/mapper/DictDetailMapper.java                |   25 
 oying-system/src/main/java/com/oying/modules/system/rest/DictController.java                    |   87 
 oying-tools/src/main/java/com/oying/domain/enums/PayStatusEnum.java                             |   52 
 oying-generator/src/main/resources/template/admin/Mapper-xml.ftl                                |   62 
 oying-system/src/main/java/com/oying/modules/system/domain/Dept.java                            |   92 
 .idea/inspectionProfiles/Project_Default.xml                                                    |    8 
 oying-system/src/main/java/com/oying/modules/quartz/task/TestTask.java                          |   26 
 .idea/encodings.xml                                                                             |   13 
 oying-system/src/main/java/com/oying/modules/security/config/LoginProperties.java               |   24 
 oying-system/src/main/resources/config/application-quartz.yml                                   |   29 
 oying-system/src/main/java/com/oying/modules/security/config/CaptchaConfig.java                 |  120 
 oying-generator/src/main/resources/mapper/GenConfigMapper.xml                                   |   27 
 oying-system/src/main/java/com/oying/modules/security/service/dto/OnlineUserDto.java            |   44 
 oying-tools/src/main/java/com/oying/rest/LocalStorageController.java                            |   85 
 LICENSE                                                                                         |  191 
 oying-common/src/main/java/com/oying/utils/EncryptUtils.java                                    |   81 
 oying-generator/src/main/resources/template/front/index.ftl                                     |  169 
 oying-system/src/main/java/com/oying/modules/system/service/impl/MonitorServiceImpl.java        |  179 
 oying-generator/src/main/resources/template/front/api.ftl                                       |   27 
 oying-system/src/main/java/com/oying/modules/system/mapper/RoleMenuMapper.java                  |   21 
 oying-common/src/main/java/com/oying/utils/ThrowableUtil.java                                   |   22 
 oying-tools/pom.xml                                                                             |   32 
 oying-system/src/main/java/com/oying/modules/system/service/DataService.java                    |   20 
 oying-system/src/main/java/com/oying/modules/system/service/impl/RoleServiceImpl.java           |  226 
 oying-generator/src/main/java/com/oying/utils/GenUtil.java                                      |  402 +
 .idea/.gitignore                                                                                |    8 
 oying-common/src/main/java/com/oying/config/mybatis/CustomP6SpyLogger.java                      |   47 
 oying-system/src/main/java/com/oying/modules/quartz/service/impl/QuartzJobServiceImpl.java      |  176 
 oying-tools/src/main/java/com/oying/domain/EmailConfig.java                                     |   41 
 oying-generator/src/main/java/com/oying/domain/dto/TableInfo.java                               |   34 
 oying-system/src/main/resources/mapper/quartz/QuartzLogMapper.xml                               |   40 
 oying-tools/src/main/java/com/oying/service/impl/EmailServiceImpl.java                          |   91 
 oying-common/src/main/java/com/oying/exception/BadRequestException.java                         |   25 
 oying-system/src/main/java/com/oying/modules/system/mapper/UserMapper.java                      |   53 
 oying-system/src/main/java/com/oying/modules/system/service/impl/JobServiceImpl.java            |  109 
 oying-common/src/main/java/com/oying/utils/CloseUtil.java                                       |   31 
 oying-system/src/main/java/com/oying/modules/security/rest/OnlineController.java                |   55 
 oying-system/src/main/java/com/oying/modules/security/security/TokenConfigurer.java             |   26 
 oying-common/src/main/java/com/oying/config/webConfig/MultipartConfig.java                      |   32 
 oying-system/src/main/java/com/oying/modules/security/security/JwtAccessDeniedHandler.java      |   28 
 oying-system/src/main/resources/mapper/system/UserRoleMapper.xml                                |   25 
 oying-tools/src/main/java/com/oying/mapper/EmailConfigMapper.java                               |   15 
 oying-system/src/main/java/com/oying/modules/system/mapper/UserJobMapper.java                   |   20 
 oying-common/src/main/java/com/oying/config/webConfig/SwaggerConfig.java                        |  152 
 oying-system/src/main/java/com/oying/modules/system/domain/dto/UserPassVo.java                  |   19 
 oying-system/src/main/java/com/oying/modules/system/service/impl/DeptServiceImpl.java           |  265 
 oying-common/src/main/java/com/oying/config/webConfig/QueryCustomizer.java                      |   18 
 oying-generator/src/main/resources/template/admin/Mapper.ftl                                    |   22 
 oying-system/src/main/java/com/oying/modules/security/service/UserDetailsServiceImpl.java       |   50 
 oying-system/src/main/java/com/oying/modules/system/domain/dto/DictDetailQueryCriteria.java     |   24 
 oying-common/src/main/java/com/oying/utils/PageUtil.java                                        |   55 
 oying-logging/src/main/resources/mapper/SysLogMapper.xml                                        |   69 
 oying-system/src/main/java/com/oying/modules/system/domain/Job.java                             |   57 
 oying-system/src/main/java/com/oying/modules/system/rest/RoleController.java                    |  139 
 oying-generator/src/main/java/com/oying/service/impl/GeneratorServiceImpl.java                  |  150 
 oying-system/src/main/java/com/oying/modules/quartz/rest/QuartzJobController.java               |  132 
 oying-system/src/main/java/com/oying/modules/quartz/utils/QuartzRunnable.java                   |   43 
 oying-system/src/main/java/com/oying/modules/system/service/RoleService.java                    |  116 
 oying-system/src/main/resources/config/application-prod.yml                                     |  129 
 oying-system/src/main/resources/template/taskAlarm.ftl                                          |   69 
 oying-system/src/main/java/com/oying/modules/quartz/domain/QuartzJob.java                       |   70 
 oying-system/src/main/java/com/oying/modules/quartz/domain/dto/QuartzJobQueryCriteria.java      |   29 
 oying-common/src/main/java/com/oying/utils/RequestHolder.java                                   |   18 
 oying-system/src/main/java/com/oying/modules/quartz/config/QuartzConfig.java                    |   51 
 oying-system/src/main/java/com/oying/modules/system/service/DeptService.java                    |  110 
 oying-system/src/main/java/com/oying/modules/system/service/VerifyService.java                  |   26 
 oying-common/src/main/java/com/oying/utils/AnonTagUtils.java                                    |   87 
 oying-generator/src/main/resources/template/admin/Controller.ftl                                |   73 
 oying-common/src/main/java/com/oying/utils/CacheKey.java                                        |   51 
 oying-system/src/main/java/com/oying/modules/system/service/MenuService.java                    |  104 
 oying-common/src/main/java/com/oying/utils/FileUtil.java                                        |  394 +
 oying-generator/src/main/java/com/oying/mapper/ColumnInfoMapper.java                            |   24 
 oying-common/src/main/java/com/oying/config/RemoveDruidAdConfig.java                            |   77 
 oying-system/src/main/resources/mapper/quartz/QuartzJobMapper.xml                               |   49 
 oying-common/src/main/java/com/oying/config/AuthorityConfig.java                                |   27 
 oying-system/src/main/java/com/oying/modules/system/domain/dto/MenuMetaVo.java                  |   24 
 oying-system/src/main/java/com/oying/modules/system/service/impl/VerifyServiceImpl.java         |   65 
 oying-tools/src/main/java/com/oying/service/LocalStorageService.java                            |   62 
 oying-system/src/main/resources/mapper/system/RoleMenuMapper.xml                                |   30 
 oying-common/src/main/java/com/oying/utils/enums/RequestMethodEnum.java                         |   58 
 oying-system/src/main/resources/logback.xml                                                     |   29 
 oying-system/src/main/resources/config/application-dev.yml                                      |  118 
 oying-system/src/main/java/com/oying/modules/quartz/service/QuartzJobService.java               |  105 
 .idea/jarRepositories.xml                                                                       |   25 
 oying-common/src/main/java/com/oying/config/RedissonConfiguration.java                          |   56 
 oying-common/src/main/java/com/oying/annotation/rest/AnonymousGetMapping.java                   |   73 
 oying-system/src/main/java/com/oying/modules/system/service/impl/DictDetailServiceImpl.java     |   83 
 oying-system/src/main/resources/mapper/system/RoleDeptMapper.xml                                |   25 
 oying-common/src/main/java/com/oying/utils/enums/CodeEnum.java                                  |   31 
 oying-generator/src/main/resources/template/admin/ServiceImpl.ftl                               |   90 
 oying-system/src/main/java/com/oying/modules/system/domain/dto/JobQueryCriteria.java            |   31 
 oying-common/src/main/java/com/oying/utils/SpringBeanHolder.java                                |  157 
 oying-system/src/main/java/com/oying/modules/system/rest/VerifyController.java                  |   61 
 .idea/compiler.xml                                                                              |   27 
 oying-system/pom.xml                                                                            |  100 
 oying-generator/src/main/java/com/oying/rest/GeneratorController.java                           |   86 
 oying-common/pom.xml                                                                            |   24 
 oying-system/src/main/java/com/oying/modules/system/mapper/RoleDeptMapper.java                  |   19 
 oying-system/src/main/java/com/oying/modules/quartz/mapper/QuartzLogMapper.java                 |   23 
 oying-common/src/main/java/com/oying/config/webConfig/WebSocketConfig.java                      |   18 
 oying-system/src/main/java/com/oying/modules/system/rest/DeptController.java                    |  112 
 oying-logging/src/main/java/com/oying/annotation/Log.java                                       |   16 
 oying-logging/src/main/java/com/oying/mapper/SysLogMapper.java                                  |   30 
 oying-common/src/main/java/com/oying/aspect/LimitAspect.java                                    |   85 
 oying-common/src/main/java/com/oying/utils/BigDecimalUtils.java                                 |  128 
 oying-system/src/main/resources/mapper/system/JobMapper.xml                                     |   36 
 oying-common/src/main/java/com/oying/annotation/rest/AnonymousPutMapping.java                   |   74 
 oying-tools/src/main/java/com/oying/mapper/LocalStorageMapper.java                              |   23 
 oying-tools/src/main/java/com/oying/service/EmailService.java                                   |   34 
 oying-system/src/main/java/com/oying/modules/system/service/UserService.java                    |  121 
 oying-logging/src/main/java/com/oying/aspect/LogAspect.java                                     |   87 
 oying-common/src/main/java/com/oying/exception/handler/GlobalExceptionHandler.java              |   97 
 oying-generator/src/main/resources/template/admin/Service.ftl                                   |   60 
 oying-logging/src/main/java/com/oying/domain/dto/SysLogQueryCriteria.java                       |   33 
 oying-generator/src/main/resources/template/admin/QueryCriteria.ftl                             |   43 
 oying-common/src/main/java/com/oying/utils/RedisUtils.java                                      |  789 ++
 oying-common/src/main/java/com/oying/annotation/rest/AnonymousAccess.java                       |   15 
 oying-tools/src/main/java/com/oying/utils/PayUtils.java                                         |   33 
 oying-generator/src/main/java/com/oying/service/impl/GenConfigServiceImpl.java                  |   54 
 .idea/sqldialects.xml                                                                           |    7 
 oying-system/src/main/java/com/oying/modules/security/security/JwtAuthenticationEntryPoint.java |   31 
 oying-system/src/main/java/com/oying/modules/security/service/OnlineUserService.java            |  133 
 oying-tools/src/main/java/com/oying/domain/dto/EmailDto.java                                    |   32 
 oying-system/src/main/java/com/oying/modules/system/service/DictDetailService.java              |   50 
 oying-system/src/main/java/com/oying/modules/system/mapper/JobMapper.java                       |   26 
 oying-generator/pom.xml                                                                         |   37 
 oying-system/src/main/java/com/oying/modules/system/domain/Dict.java                            |   34 
 oying-system/src/main/java/com/oying/modules/security/service/dto/JwtUserDto.java               |   69 
 oying-logging/pom.xml                                                                           |   20 
 oying-common/src/main/java/com/oying/annotation/rest/AnonymousPatchMapping.java                 |   74 
 oying-generator/src/main/java/com/oying/domain/GenConfig.java                                   |   63 
 oying-system/src/main/java/com/oying/modules/system/service/impl/MenuServiceImpl.java           |  351 +
 oying-common/src/main/java/com/oying/utils/DateUtil.java                                        |  153 
 oying-system/src/main/java/com/oying/modules/system/domain/DictDetail.java                      |   44 
 oying-tools/src/main/java/com/oying/domain/LocalStorage.java                                    |   57 
 oying-system/src/main/resources/mapper/system/DictDetailMapper.xml                              |   53 
 oying-system/src/main/java/com/oying/modules/security/service/UserCacheManager.java             |   70 
 oying-generator/src/main/resources/template/admin/Entity.ftl                                    |   72 
 oying-system/src/main/java/com/oying/modules/system/domain/dto/UserQueryCriteria.java           |   44 
 oying-system/src/main/resources/mapper/system/UserJobMapper.xml                                 |   25 
 oying-system/src/main/resources/banner.txt                                                      |   15 
 oying-common/src/main/java/com/oying/utils/ElConstant.java                                      |   19 
 oying-system/src/main/java/com/oying/modules/system/domain/dto/RoleQueryCriteria.java           |   29 
 oying-common/src/main/java/com/oying/exception/EntityNotFoundException.java                     |   19 
 oying-logging/src/main/java/com/oying/domain/SysLog.java                                        |   66 
 oying-system/src/main/resources/mapper/system/RoleMapper.xml                                    |  126 
 oying-system/src/main/java/com/oying/modules/system/domain/dto/DeptQueryCriteria.java           |   32 
 oying-common/src/main/java/com/oying/config/mybatis/MybatisPlusConfig.java                      |   27 
 oying-system/src/main/java/com/oying/modules/system/domain/Role.java                            |   74 
 README.md                                                                                       |   86 
 oying-common/src/main/java/com/oying/annotation/rest/AnonymousDeleteMapping.java                |   74 
 oying-system/src/main/java/com/oying/modules/system/service/MonitorService.java                 |   16 
 oying-generator/src/main/java/com/oying/utils/ColUtil.java                                      |   39 
 oying-system/src/main/java/com/oying/modules/system/mapper/RoleMapper.java                      |   37 
 oying-common/src/main/java/com/oying/utils/StringUtils.java                                     |  228 
 oying-system/src/main/java/com/oying/modules/quartz/utils/ExecutionJob.java                     |  116 
 oying-common/src/main/java/com/oying/utils/SecurityUtils.java                                   |  129 
 oying-system/src/main/java/com/oying/modules/system/service/impl/UserServiceImpl.java           |  279 +
 oying-system/src/main/java/com/oying/modules/security/security/TokenProvider.java               |  131 
 oying-system/src/main/java/com/oying/modules/quartz/domain/QuartzLog.java                       |   48 
 oying-generator/src/main/java/com/oying/service/GeneratorService.java                           |   79 
 oying-generator/src/main/resources/gen.properties                                               |   27 
 oying-common/src/main/java/com/oying/config/RedisConfiguration.java                             |  176 
 oying-common/src/main/java/com/oying/utils/enums/CodeBiEnum.java                                |   35 
 oying-common/src/main/java/com/oying/utils/RsaUtils.java                                        |  198 
 oying-system/src/main/java/com/oying/modules/quartz/utils/QuartzManage.java                     |  162 
 oying-generator/src/main/resources/mapper/ColumnInfoMapper.xml                                  |   49 
 oying-system/src/main/java/com/oying/modules/security/security/TokenFilter.java                 |   77 
 oying-system/src/main/java/com/oying/modules/system/mapper/DeptMapper.java                      |   32 
 oying-system/src/main/java/com/oying/modules/system/rest/DictDetailController.java              |   82 
 oying-common/src/main/java/com/oying/config/properties/FileProperties.java                      |   45 
 oying-system/src/main/java/com/oying/modules/security/service/dto/AuthorityDto.java             |   21 
 oying-system/src/main/java/com/oying/modules/system/domain/dto/MenuQueryCriteria.java           |   26 
 oying-system/src/main/resources/mapper/system/UserMapper.xml                                    |  178 
 oying-system/src/main/java/com/oying/modules/system/service/impl/DictServiceImpl.java           |  107 
 oying-system/src/main/java/com/oying/sysrunner/SystemRunner.java                                |   22 
 oying-system/src/main/java/com/oying/modules/security/config/SpringSecurityConfig.java          |  119 
 oying-system/src/main/java/com/oying/modules/system/rest/UserController.java                    |  197 
 oying-system/src/main/resources/config/application.yml                                          |   73 
 oying-system/src/main/java/com/oying/modules/system/service/DictService.java                    |   61 
 oying-common/src/main/java/com/oying/annotation/rest/AnonymousPostMapping.java                  |   74 
 oying-system/src/main/java/com/oying/modules/quartz/mapper/QuartzJobMapper.java                 |   26 
 oying-tools/src/main/java/com/oying/service/impl/LocalStorageServiceImpl.java                   |  110 
 oying-tools/src/main/java/com/oying/rest/EmailController.java                                   |   48 
 oying-generator/src/main/java/com/oying/mapper/GenConfigMapper.java                             |   16 
 oying-logging/src/main/java/com/oying/rest/SysLogController.java                                |  100 
 oying-system/src/main/java/com/oying/modules/system/domain/Menu.java                            |  111 
 oying-system/src/main/java/com/oying/modules/security/rest/AuthController.java                  |  139 
 oying-system/src/main/java/com/oying/modules/system/rest/LimitController.java                   |   32 
 231 files changed, 16,647 insertions(+), 2 deletions(-)

diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..35410ca
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..8f29a85
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <annotationProcessing>
+      <profile default="true" name="Default" enabled="true" />
+      <profile name="Maven default annotation processors profile" enabled="true">
+        <sourceOutputDir name="target/generated-sources/annotations" />
+        <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+        <outputRelativeToContentRoot value="true" />
+        <module name="oying-system" />
+        <module name="oying-tools" />
+        <module name="oying-logging" />
+        <module name="oying-common" />
+        <module name="oying-generator" />
+      </profile>
+    </annotationProcessing>
+  </component>
+  <component name="JavacSettings">
+    <option name="ADDITIONAL_OPTIONS_OVERRIDE">
+      <module name="oying-common" options="-parameters" />
+      <module name="oying-generator" options="-parameters" />
+      <module name="oying-logging" options="-parameters" />
+      <module name="oying-system" options="-parameters" />
+      <module name="oying-tools" options="-parameters" />
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..6e24ddc
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
+    <data-source source="LOCAL" name="oying" uuid="ae90bd7e-992f-4e9a-bff6-a82171b032ef">
+      <driver-ref>mysql.8</driver-ref>
+      <synchronize>true</synchronize>
+      <jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
+      <jdbc-url>jdbc:mysql://118.145.136.116:3306/oying</jdbc-url>
+      <jdbc-additional-properties>
+        <property name="com.intellij.clouds.kubernetes.db.host.port" />
+        <property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
+        <property name="com.intellij.clouds.kubernetes.db.resource.type" value="Deployment" />
+        <property name="com.intellij.clouds.kubernetes.db.container.port" />
+      </jdbc-additional-properties>
+      <working-dir>$ProjectFileDir$</working-dir>
+    </data-source>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..9536954
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
+    <file url="file://$PROJECT_DIR$/oying-common/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/oying-generator/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/oying-logging/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/oying-system/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/oying-tools/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
+    <file url="PROJECT" charset="UTF-8" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..68ce32a
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,8 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="JavadocDeclaration" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="ADDITIONAL_TAGS" value="date,description" />
+    </inspection_tool>
+  </profile>
+</component>
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..dfaf1c0
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RemoteRepositoriesConfiguration">
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Central Repository" />
+      <option name="url" value="https://repo.maven.apache.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="public" />
+      <option name="name" value="aliyun nexus" />
+      <option name="url" value="http://maven.aliyun.com/nexus/content/groups/public/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Maven Central repository" />
+      <option name="url" value="https://repo1.maven.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="jboss.community" />
+      <option name="name" value="JBoss Community repository" />
+      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
+    </remote-repository>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..d5cd614
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="MavenProjectsManager">
+    <option name="originalFiles">
+      <list>
+        <option value="$PROJECT_DIR$/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
+</project>
\ No newline at end of file
diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
new file mode 100644
index 0000000..539d872
--- /dev/null
+++ b/.idea/sqldialects.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="SqlDialectMappings">
+    <file url="file://$PROJECT_DIR$" dialect="MySQL" />
+    <file url="PROJECT" dialect="MySQL" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..bca89d6
--- /dev/null
+++ b/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-2025 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.
\ No newline at end of file
diff --git a/README.md b/README.md
index 8c75e49..61434f7 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,85 @@
-## oyingServer
+**账号密码:** `admin/123456`  
 
-start time:2025/4/14
+**MYSQL:** `118.145.136.116:3306/oying/Ik52981698`
+
+公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCNSBMdggSm8sL1Zjb30cnLcubsrLu2X3i3TsCbLeHAAmj4YXUlKuQqnQ0THGXkWzsrfHnNPWbc+arVOlXvmMDh7sVD8rNCBjksa1xeVPNA5yPLseJPNsPDKAo7QdJshfELAmghfrgwYWxtf8kG8oWd6Vq2aR9p/Zo2orU7WkFXTQIDAQAB  
+私钥:MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAI1IEx2CBKbywvVmNvfRycty5uysu7ZfeLdOwJst4cACaPhhdSUq5CqdDRMcZeRbOyt8ec09Ztz5qtU6Ve+YwOHuxUPys0IGOSxrXF5U80DnI8ux4k82w8MoCjtB0myF8QsCaCF+uDBhbG1/yQbyhZ3pWrZpH2n9mjaitTtaQVdNAgMBAAECgYA6oLot+JJtpTf6FdyholEXOCtT86pB2ASELQ4IV1XjFBzzVZ4DOnVMqbePQq2VwbYgKZtx7BUPhhu6OGcI8l63v8OTAgoNovCksSA8rSPfCs593JmKFVShsHApkHAH/Klo/PsEV0QvpG9Uf0hTOdNiqbHWAorA/PnuaBr0/anygQJBAPdtuflC9JxPxaySBz2Up7g1QG9xHW459U/2M0Mn/EI+RJdd7vjITeofZ52yIElOmmgjU8XK3edncE1H1YhaLbkCQQCSLQDKcDFHIw0KRm0a51nTknoi23ZloxQEt3/86zJurzheujX3Oo9cY5FvzlvHKWqgAvAiBiivt9hGWnxeNOA1AkEAiHkpPudDbIRDj+/rtnesGtqkc9N8XDPzruspUz1W0mLuCl9xVB+Hej9gM4bwb/6/A/mYV1ySEPTo6HdavB6hYQJAJv8yks9TljLXq8IWIXNPF46gXuRFtd/H22pJDuSAU98THtJ2yzooPPGjPzzCZ2O5Om8OOUWDXT2iyUIio89fcQJAUYS2803tLFouTzaa+SR3kpuqEi6en1yrJNGDe7a4tfGjhQBLmjhCOMuZAjkYYfnN8HiN+dRdKMu/8rpPpC0NKg==
+
+
+#### 项目结构
+项目采用按功能分模块的开发方式,结构如下
+
+- `oying-common` 为系统的公共模块,各种工具类,公共配置存在该模块
+
+- `oying-system` 为系统核心模块也是项目入口模块,也是最终需要打包部署的模块
+
+- `oying-logging` 为系统的日志模块,其他模块如果需要记录日志需要引入该模块
+
+- `oying-tools` 为第三方工具模块,包含:邮件、存储
+
+- `oying-generator` 为系统的代码生成模块,支持生成前后端CRUD代码
+
+#### 详细结构
+
+```
+- oying-common 公共模块
+    - annotation 为系统自定义注解
+    - aspect 自定义注解的切面
+    - base 提供了 Entity 基类
+    - config 项目通用配置
+        - Mybatis-Plus 配置
+        - Web配置跨域与静态资源映射、Swagger配置,文件上传临时路径配置
+        - Redis配置,Redission配置, 异步线程池配置
+        - 权限拦截配置:AuthorityConfig、Druid 删除广告配置
+    - exception 项目统一异常的处理
+    - utils 系统通用工具类,列举一些常用的工具类
+        - BigDecimaUtils 金额计算工具类
+        - RequestHolder 请求工具类
+        - SecurityUtils 安全工具类
+        - StringUtils 字符串工具类
+        - SpringBeanHolder Spring Bean工具类
+        - RedisUtils Redis工具类
+        - EncryptUtils 加密工具类
+        - FileUtil 文件工具类
+- oying-system 系统核心模块(系统启动入口)
+    - sysrunner 程序启动后处理数据
+	- modules 系统相关模块(登录授权、系统监控、定时任务、系统模块)
+- oying-logging 系统日志模块
+- oying-tools 系统第三方工具模块
+    - email 邮件工具
+    - local-storage 存储工具
+- oying-generator 系统代码生成模块
+```
+
+一、*基础信息*
+1. **用户(User)**:包括所有用户角色(商户、消费者、骑手、代货商、拉新代理等),需要通过角色字段区分。
+
+2. **门店(Store)**:商户可以拥有多个门店,每个门店有独立的信息。
+
+3. **商品(Product)**:由商户或代货商发布,需要包含商品名称、价格、库存等信息,可能关联到分类和参数。
+
+4. **商品分类(Category)**:后台统一维护的商品分类。
+
+5. **订单(Order)**:消费者下单生成的订单和明细,包含订单状态、商品列表、价格、配送信息等。
+
+6. **骑手(Rider)**:负责配送订单的骑手信息。
+
+7. **配送(Delivery)**:订单的配送信息,关联骑手和订单。
+
+8. **评价(Review)**:消费者对商品和服务的评价。
+
+9. **库存(Inventory)**:<font color="red">(库存维护)</font>商品的库存信息,可能关联到门店。
+
+10. **提现记录(Withdrawal)**:商户和骑手的提现记录。<font color="red">(分成规则)</font>
+
+11. **消息(Message)**:系统消息、订单消息、顾客留言等。
+
+12. **数据统计(Statistics)**:存储各类统计信息,如订单数量、销售额等
+
+二、 *第三方信息*
+1. **支付方式**
+2. **地图服务**
+3. **小票打印**
+4. **短信服务**
+5. **云存储服务**
+
diff --git a/oying-common/pom.xml b/oying-common/pom.xml
new file mode 100644
index 0000000..f753c82
--- /dev/null
+++ b/oying-common/pom.xml
@@ -0,0 +1,24 @@
+<?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>oying</artifactId>
+        <groupId>com.oying</groupId>
+        <version>1.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <properties>
+        <hutool.version>5.8.35</hutool.version>
+    </properties>
+
+    <artifactId>oying-common</artifactId>
+    <name>公共模块</name>
+
+    <dependencies>
+        <!--工具包-->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>${hutool.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/oying-common/src/main/java/com/oying/annotation/Limit.java b/oying-common/src/main/java/com/oying/annotation/Limit.java
new file mode 100644
index 0000000..f96f883
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/annotation/Limit.java
@@ -0,0 +1,34 @@
+package com.oying.annotation;
+
+import com.oying.aspect.LimitType;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author Z
+ */
+@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;
+
+}
diff --git a/oying-common/src/main/java/com/oying/annotation/rest/AnonymousAccess.java b/oying-common/src/main/java/com/oying/annotation/rest/AnonymousAccess.java
new file mode 100644
index 0000000..293fc0e
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/annotation/rest/AnonymousAccess.java
@@ -0,0 +1,15 @@
+package com.oying.annotation.rest;
+
+import java.lang.annotation.*;
+
+/**
+ * @author Z
+ *  用于标记匿名访问方法
+ */
+@Inherited
+@Documented
+@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AnonymousAccess {
+
+}
diff --git a/oying-common/src/main/java/com/oying/annotation/rest/AnonymousDeleteMapping.java b/oying-common/src/main/java/com/oying/annotation/rest/AnonymousDeleteMapping.java
new file mode 100644
index 0000000..250a7c5
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/annotation/rest/AnonymousDeleteMapping.java
@@ -0,0 +1,74 @@
+package com.oying.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 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 Z
+ * @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 {};
+
+}
diff --git a/oying-common/src/main/java/com/oying/annotation/rest/AnonymousGetMapping.java b/oying-common/src/main/java/com/oying/annotation/rest/AnonymousGetMapping.java
new file mode 100644
index 0000000..97fb66c
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/annotation/rest/AnonymousGetMapping.java
@@ -0,0 +1,73 @@
+package com.oying.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 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 Z
+ * @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 {};
+
+}
diff --git a/oying-common/src/main/java/com/oying/annotation/rest/AnonymousPatchMapping.java b/oying-common/src/main/java/com/oying/annotation/rest/AnonymousPatchMapping.java
new file mode 100644
index 0000000..0fcdbb4
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/annotation/rest/AnonymousPatchMapping.java
@@ -0,0 +1,74 @@
+package com.oying.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 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 Z
+ * @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 {};
+
+}
diff --git a/oying-common/src/main/java/com/oying/annotation/rest/AnonymousPostMapping.java b/oying-common/src/main/java/com/oying/annotation/rest/AnonymousPostMapping.java
new file mode 100644
index 0000000..1c6fbd8
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/annotation/rest/AnonymousPostMapping.java
@@ -0,0 +1,74 @@
+package com.oying.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 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 Z
+ * @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 {};
+
+}
diff --git a/oying-common/src/main/java/com/oying/annotation/rest/AnonymousPutMapping.java b/oying-common/src/main/java/com/oying/annotation/rest/AnonymousPutMapping.java
new file mode 100644
index 0000000..509d336
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/annotation/rest/AnonymousPutMapping.java
@@ -0,0 +1,74 @@
+package com.oying.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 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 Z
+ * @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 {};
+
+}
diff --git a/oying-common/src/main/java/com/oying/aspect/LimitAspect.java b/oying-common/src/main/java/com/oying/aspect/LimitAspect.java
new file mode 100644
index 0000000..98aa02c
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/aspect/LimitAspect.java
@@ -0,0 +1,85 @@
+package com.oying.aspect;
+
+import cn.hutool.core.util.ObjUtil;
+import com.google.common.collect.ImmutableList;
+import com.oying.annotation.Limit;
+import com.oying.exception.BadRequestException;
+import com.oying.utils.RequestHolder;
+import com.oying.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 Z
+ */
+@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(com.oying.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<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
+        Long count = redisTemplate.execute(redisScript, keys, limit.count(), limit.period());
+        if (ObjUtil.isNotNull(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;";
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/aspect/LimitType.java b/oying-common/src/main/java/com/oying/aspect/LimitType.java
new file mode 100644
index 0000000..575ebac
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/aspect/LimitType.java
@@ -0,0 +1,12 @@
+package com.oying.aspect;
+
+/**
+ * 限流枚举
+ * @author Z
+ */
+public enum LimitType {
+    // 默认
+    CUSTOMER,
+    //  by ip addr
+    IP
+}
diff --git a/oying-common/src/main/java/com/oying/base/BaseEntity.java b/oying-common/src/main/java/com/oying/base/BaseEntity.java
new file mode 100644
index 0000000..859ec81
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/base/BaseEntity.java
@@ -0,0 +1,65 @@
+package com.oying.base;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.springframework.data.annotation.CreatedBy;
+import org.springframework.data.annotation.LastModifiedBy;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.sql.Timestamp;
+
+/**
+ * 通用字段, is_del 根据需求自行添加
+ * @author Z
+ * @date 2019年10月24日20:46:32
+ */
+@Getter
+@Setter
+public class BaseEntity implements Serializable {
+
+    @CreatedBy
+    @TableField(fill = FieldFill.INSERT)
+    @ApiModelProperty(value = "创建人", hidden = true)
+    private String createBy;
+
+    @LastModifiedBy
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @ApiModelProperty(value = "更新人", hidden = true)
+    private String updateBy;
+
+    @TableField(fill = FieldFill.INSERT)
+    @ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true)
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
+    private Timestamp createTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @ApiModelProperty(value = "更新时间: yyyy-MM-dd HH:mm:ss", hidden = true)
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
+    private Timestamp 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();
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/config/AsyncExecutor.java b/oying-common/src/main/java/com/oying/config/AsyncExecutor.java
new file mode 100644
index 0000000..4e577cd
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/AsyncExecutor.java
@@ -0,0 +1,80 @@
+package com.oying.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 创建自定义的线程池
+ * @author Z
+ * @description
+ * @date 2023-06-08
+ **/
+@EnableAsync
+@Configuration
+public class AsyncExecutor implements AsyncConfigurer {
+
+    public static int corePoolSize;
+
+    public static int maxPoolSize;
+
+    public static int keepAliveSeconds;
+
+    public static int queueCapacity;
+
+    @Value("${task.pool.core-pool-size}")
+    public void setCorePoolSize(int corePoolSize) {
+        AsyncExecutor.corePoolSize = corePoolSize;
+    }
+
+    @Value("${task.pool.max-pool-size}")
+    public void setMaxPoolSize(int maxPoolSize) {
+        AsyncExecutor.maxPoolSize = maxPoolSize;
+    }
+
+    @Value("${task.pool.keep-alive-seconds}")
+    public void setKeepAliveSeconds(int keepAliveSeconds) {
+        AsyncExecutor.keepAliveSeconds = keepAliveSeconds;
+    }
+
+    @Value("${task.pool.queue-capacity}")
+    public void setQueueCapacity(int queueCapacity) {
+        AsyncExecutor.queueCapacity = queueCapacity;
+    }
+
+    /**
+     * 自定义线程池,用法 @Async
+     * @return Executor
+     */
+    @Override
+    public Executor getAsyncExecutor() {
+        // 自定义工厂
+        ThreadFactory factory = r -> new Thread(r, "el-async-" + new AtomicInteger(1).getAndIncrement());
+        // 自定义线程池
+        return new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveSeconds,
+                TimeUnit.SECONDS, new ArrayBlockingQueue<>(queueCapacity), factory,
+                new ThreadPoolExecutor.CallerRunsPolicy());
+    }
+
+    /**
+     * 自定义线程池,用法,注入到类中使用
+     * private ThreadPoolTaskExecutor taskExecutor;
+     * @return ThreadPoolTaskExecutor
+     */
+    @Bean("taskAsync")
+    public ThreadPoolTaskExecutor taskAsync() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setCorePoolSize(2);
+        executor.setMaxPoolSize(4);
+        executor.setQueueCapacity(20);
+        executor.setKeepAliveSeconds(60);
+        executor.setThreadNamePrefix("el-task-");
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        return executor;
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/config/AuthorityConfig.java b/oying-common/src/main/java/com/oying/config/AuthorityConfig.java
new file mode 100644
index 0000000..26d5a00
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/AuthorityConfig.java
@@ -0,0 +1,27 @@
+package com.oying.config;
+
+import com.oying.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 Z
+ */
+@Service(value = "el")
+public class AuthorityConfig {
+
+    /**
+     * 判断接口是否有权限
+     * @param permissions 权限
+     * @return /
+     */
+    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);
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/config/RedisConfiguration.java b/oying-common/src/main/java/com/oying/config/RedisConfiguration.java
new file mode 100644
index 0000000..c5fa583
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/RedisConfiguration.java
@@ -0,0 +1,176 @@
+package com.oying.config;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONFactory;
+import com.alibaba.fastjson2.JSONWriter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.MurmurHash3;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+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.cache.interceptor.SimpleCacheErrorHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Z
+ * @date 2025-01-13
+ */
+@Slf4j
+@Configuration
+@EnableCaching
+@AutoConfigureBefore(RedisAutoConfiguration.class)
+public class RedisConfiguration extends CachingConfigurerSupport {
+
+    // 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
+    private static final String[] WHITELIST_STR = {"com.oying" };
+
+    /**
+     *  设置 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;
+    }
+
+    @Bean(name = "redisTemplate")
+    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisTemplate<Object, Object> template = new RedisTemplate<>();
+        // 指定 key 和 value 的序列化方案
+        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
+        // value值的序列化采用fastJsonRedisSerializer
+        template.setValueSerializer(fastJsonRedisSerializer);
+        template.setHashValueSerializer(fastJsonRedisSerializer);
+        // 设置fastJson的序列化白名单
+        for (String pack : WHITELIST_STR) {
+            JSONFactory.getDefaultObjectReaderProvider().addAutoTypeAccept(pack);
+        }
+        // key的序列化采用StringRedisSerializer
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setConnectionFactory(redisConnectionFactory);
+        return template;
+    }
+
+    /**
+     * 缓存管理器
+     * @param redisConnectionFactory /
+     * @return 缓存管理器
+     */
+    @Bean
+    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
+        RedisCacheConfiguration config = redisCacheConfiguration();
+        return RedisCacheManager.builder(redisConnectionFactory)
+                .cacheDefaults(config)
+                .build();
+    }
+
+    /**
+     * 自定义缓存key生成策略
+     */
+    @Bean
+    public KeyGenerator keyGenerator() {
+        return (target, method, params) -> {
+            Map<String,Object> container = new HashMap<>(8);
+            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);
+            // 使用 MurmurHash 生成 hash
+            return Integer.toHexString(MurmurHash3.hash32x86(jsonString.getBytes()));
+        };
+    }
+
+    @Bean
+    @SuppressWarnings({"unchecked","all"})
+    public CacheErrorHandler errorHandler() {
+        return new SimpleCacheErrorHandler() {
+            @Override
+            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
+                // 处理缓存读取错误
+                log.error("Cache Get Error: {}",exception.getMessage());
+            }
+            @Override
+            public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
+                // 处理缓存写入错误
+                log.error("Cache Put Error: {}",exception.getMessage());
+            }
+            @Override
+            public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
+                // 处理缓存删除错误
+                log.error("Cache Evict Error: {}",exception.getMessage());
+            }
+            @Override
+            public void handleCacheClearError(RuntimeException exception, Cache cache) {
+                // 处理缓存清除错误
+                log.error("Cache Clear Error: {}",exception.getMessage());
+            }
+        };
+    }
+
+    /**
+     * Value 序列化
+     *
+     * @param <T>
+     * @author Z
+     */
+    static 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) throws SerializationException
+        {
+            if (t == null) {
+                return new byte[0];
+            }
+            return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(StandardCharsets.UTF_8);
+        }
+
+        @Override
+        public T deserialize(byte[] bytes) throws SerializationException
+        {
+            if (bytes == null || bytes.length == 0) {
+                return null;
+            }
+            String str = new String(bytes, StandardCharsets.UTF_8);
+            return JSON.parseObject(str, clazz);
+        }
+
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/config/RedissonConfiguration.java b/oying-common/src/main/java/com/oying/config/RedissonConfiguration.java
new file mode 100644
index 0000000..1b93d19
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/RedissonConfiguration.java
@@ -0,0 +1,56 @@
+package com.oying.config;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Data;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Data
+@Configuration
+@AutoConfigureBefore(RedisAutoConfiguration.class)
+public class RedissonConfiguration {
+
+    @Value("${spring.redis.host}")
+    private String redisHost;
+
+    @Value("${spring.redis.port}")
+    private int redisPort;
+
+    @Value("${spring.redis.database}")
+    private int redisDatabase;
+
+    @Value("${spring.redis.password:}")
+    private String redisPassword;
+
+    @Value("${spring.redis.timeout:5000}")
+    private int timeout;
+
+    @Value("${spring.redis.lettuce.pool.max-active:64}")
+    private int connectionPoolSize;
+
+    @Value("${spring.redis.lettuce.pool.min-idle:16}")
+    private int connectionMinimumIdleSize;
+
+    @Bean
+    public RedissonClient redissonClient() {
+        Config config = new Config();
+        config.useSingleServer()
+                .setAddress("redis://" + redisHost + ":" + redisPort)
+                .setDatabase(redisDatabase)
+                .setTimeout(timeout)
+                .setConnectionPoolSize(connectionPoolSize)
+                .setConnectionMinimumIdleSize(connectionMinimumIdleSize);
+        if(StrUtil.isNotBlank(redisPassword)){
+            config.useSingleServer().setPassword(redisPassword);
+        }
+        return Redisson.create(config);
+    }
+}
+
+
diff --git a/oying-common/src/main/java/com/oying/config/RemoveDruidAdConfig.java b/oying-common/src/main/java/com/oying/config/RemoveDruidAdConfig.java
new file mode 100644
index 0000000..86e8e95
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/RemoveDruidAdConfig.java
@@ -0,0 +1,77 @@
+package com.oying.config;
+
+import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
+import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
+import com.alibaba.druid.util.Utils;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Z
+ * @description
+ * @date 2025-01-11
+ **/
+@Configuration
+@SuppressWarnings({"unchecked","all"})
+@ConditionalOnWebApplication
+@AutoConfigureAfter(DruidDataSourceAutoConfigure.class)
+@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled",
+        havingValue = "true", matchIfMissing = true)
+public class RemoveDruidAdConfig {
+
+    /**
+     * 方法名: removeDruidAdFilterRegistrationBean
+     * 方法描述 除去页面底部的广告
+     * @param properties com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties
+     * @return org.springframework.boot.web.servlet.FilterRegistrationBean
+     */
+    @Bean
+    public FilterRegistrationBean removeDruidAdFilterRegistrationBean(DruidStatProperties properties) {
+
+        // 获取web监控页面的参数
+        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
+        // 提取common.js的配置路径
+        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
+        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
+
+        final String filePath = "support/http/resources/js/common.js";
+
+        //创建filter进行过滤
+        Filter filter = new Filter() {
+            @Override
+            public void init(FilterConfig filterConfig) throws ServletException {}
+
+            @Override
+            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+                HttpServletRequest httpRequest = (HttpServletRequest) request;
+                HttpServletResponse httpResponse = (HttpServletResponse) response;
+                if (httpRequest.getRequestURI().endsWith("js/common.js")) {
+                    // 获取common.js
+                    String text = Utils.readFromResource(filePath);
+                    // 正则替换banner, 除去底部的广告信息
+                    text = text.replaceAll("<a.*?druid_banner\"></a><br/>", "");
+                    text = text.replaceAll("powered by.*?shrek.wang</a>", "");
+                    httpResponse.setContentType("application/javascript");
+                    httpResponse.setCharacterEncoding("UTF-8");
+                    httpResponse.getWriter().write(text);
+                } else {
+                    chain.doFilter(request, response);
+                }
+            }
+            @Override
+            public void destroy() {}
+        };
+        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
+        registrationBean.setFilter(filter);
+        registrationBean.addUrlPatterns(commonJsPattern);
+        return registrationBean;
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/config/mybatis/CustomP6SpyLogger.java b/oying-common/src/main/java/com/oying/config/mybatis/CustomP6SpyLogger.java
new file mode 100644
index 0000000..5e4e319
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/mybatis/CustomP6SpyLogger.java
@@ -0,0 +1,47 @@
+package com.oying.config.mybatis;
+
+import cn.hutool.core.util.StrUtil;
+import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author Z
+ * @description 自定义 p6spy sql输出格式
+ * @date 2024-12-26
+ **/
+@Slf4j
+public class CustomP6SpyLogger implements MessageFormattingStrategy {
+
+    // 重置颜色
+    private static final String RESET = "\u001B[0m";
+    // 红色
+    private static final String RED = "\u001B[31m";
+    // 绿色
+    private static final String GREEN = "\u001B[32m";
+
+    /**
+     * 格式化 sql
+     * @param connectionId 连接id
+     * @param now 当前时间
+     * @param elapsed 执行时长
+     * @param category sql分类
+     * @param prepared 预编译sql
+     * @param sql 执行sql
+     * @param url 数据库连接url
+     * @return 格式化后的sql
+     */
+    @Override
+    public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) {
+        // 去掉换行和多余空格
+        if(StrUtil.isNotBlank(sql)){
+            sql = sql.replaceAll("\\s+", " ").trim();
+        }
+
+        // 格式化并加上颜色
+        return String.format(
+                "sql- %s%s%s %s[Time: %dms]%s - %s;",
+                RED, now, RESET, GREEN, elapsed, RESET, sql
+        );
+    }
+}
+
diff --git a/oying-common/src/main/java/com/oying/config/mybatis/MyMetaObjectHandler.java b/oying-common/src/main/java/com/oying/config/mybatis/MyMetaObjectHandler.java
new file mode 100644
index 0000000..243135f
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/mybatis/MyMetaObjectHandler.java
@@ -0,0 +1,40 @@
+package com.oying.config.mybatis;
+
+import cn.hutool.core.date.DateTime;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.oying.utils.SecurityUtils;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+import java.sql.Timestamp;
+
+/**
+ * @author Z
+ * @description
+ * @date 2023-06-13
+ **/
+@Component
+public class MyMetaObjectHandler implements MetaObjectHandler {
+
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        /* 创建时间 */
+        this.strictInsertFill(metaObject, "createTime", Timestamp.class, DateTime.now().toTimestamp());
+        this.strictInsertFill(metaObject, "updateTime", Timestamp.class, DateTime.now().toTimestamp());
+        /* 操作人 */
+        String username = "System";
+        try {username = SecurityUtils.getCurrentUsername();}catch (Exception ignored){}
+        this.strictInsertFill(metaObject, "createBy", String.class, username);
+        this.strictInsertFill(metaObject, "updateBy", String.class, username);
+    }
+
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        /* 更新时间 */
+        this.strictUpdateFill(metaObject, "updateTime", Timestamp.class, DateTime.now().toTimestamp());
+        /* 操作人 */
+        String username = "System";
+        try {username = SecurityUtils.getCurrentUsername();}catch (Exception ignored){}
+        this.strictUpdateFill(metaObject, "updateBy", String.class, username);
+    }
+}
+
diff --git a/oying-common/src/main/java/com/oying/config/mybatis/MybatisPlusConfig.java b/oying-common/src/main/java/com/oying/config/mybatis/MybatisPlusConfig.java
new file mode 100644
index 0000000..4a0524b
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/mybatis/MybatisPlusConfig.java
@@ -0,0 +1,27 @@
+package com.oying.config.mybatis;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Z
+ * @description
+ * @date 2023-06-12
+ **/
+@Configuration
+public class MybatisPlusConfig {
+
+    /**
+     * MyBatisPlus拦截器(用于分页)
+     */
+    @Bean
+    public MybatisPlusInterceptor paginationInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        //添加MySQL的分页拦截器
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        return interceptor;
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/config/properties/FileProperties.java b/oying-common/src/main/java/com/oying/config/properties/FileProperties.java
new file mode 100644
index 0000000..2290550
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/properties/FileProperties.java
@@ -0,0 +1,45 @@
+package com.oying.config.properties;
+
+import lombok.Data;
+import com.oying.utils.ElConstant;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Z
+ */
+@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(ElConstant.WIN)) {
+            return windows;
+        } else if(os.toLowerCase().startsWith(ElConstant.MAC)){
+            return mac;
+        }
+        return linux;
+    }
+
+    @Data
+    public static class ElPath{
+
+        private String path;
+
+        private String avatar;
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/config/properties/RsaProperties.java b/oying-common/src/main/java/com/oying/config/properties/RsaProperties.java
new file mode 100644
index 0000000..5858645
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/properties/RsaProperties.java
@@ -0,0 +1,22 @@
+package com.oying.config.properties;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Z
+ * @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;
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/config/webConfig/ConfigurerAdapter.java b/oying-common/src/main/java/com/oying/config/webConfig/ConfigurerAdapter.java
new file mode 100644
index 0000000..b8b60b1
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/webConfig/ConfigurerAdapter.java
@@ -0,0 +1,82 @@
+package com.oying.config.webConfig;
+
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.support.config.FastJsonConfig;
+import com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter;
+import com.oying.config.properties.FileProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * WebMvcConfigurer
+ *
+ * @author Z
+ * @date 2018-11-30
+ */
+@Configuration
+@EnableWebMvc
+public class ConfigurerAdapter implements WebMvcConfigurer {
+
+    /** 文件配置 */
+    private final FileProperties properties;
+
+    public ConfigurerAdapter(FileProperties properties) {
+        this.properties = properties;
+    }
+
+    @Bean
+    public CorsFilter corsFilter() {
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        CorsConfiguration config = new CorsConfiguration();
+        config.setAllowCredentials(true);
+        config.addAllowedOriginPattern("*");
+        config.addAllowedHeader("*");
+        config.addAllowedMethod("*");
+        source.registerCorsConfiguration("/**", config);
+        return new CorsFilter(source);
+    }
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        FileProperties.ElPath path = properties.getPath();
+        String avatarUtl = "file:" + path.getAvatar().replace("\\","/");
+        String pathUtl = "file:" + path.getPath().replace("\\","/");
+        registry.addResourceHandler("/avatar/**").addResourceLocations(avatarUtl).setCachePeriod(0);
+        registry.addResourceHandler("/file/**").addResourceLocations(pathUtl).setCachePeriod(0);
+        registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0);
+    }
+
+    @Override
+    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+        // 添加默认的 StringHttpMessageConverter
+        converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
+        // 配置 FastJsonHttpMessageConverter
+        FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter();
+        List<MediaType> supportMediaTypeList = new ArrayList<>();
+        supportMediaTypeList.add(MediaType.APPLICATION_JSON);
+        FastJsonConfig config = new FastJsonConfig();
+        config.setDateFormat("yyyy-MM-dd HH:mm:ss");
+        // 开启引用检测,枚举支持
+        config.setWriterFeatures(
+                JSONWriter.Feature.WriteEnumUsingToString,
+                JSONWriter.Feature.ReferenceDetection
+        );
+        fastJsonConverter.setFastJsonConfig(config);
+        fastJsonConverter.setSupportedMediaTypes(supportMediaTypeList);
+        fastJsonConverter.setDefaultCharset(StandardCharsets.UTF_8);
+        // 将 FastJsonHttpMessageConverter 添加到列表末尾
+        converters.add(fastJsonConverter);
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/config/webConfig/MultipartConfig.java b/oying-common/src/main/java/com/oying/config/webConfig/MultipartConfig.java
new file mode 100644
index 0000000..7301762
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/webConfig/MultipartConfig.java
@@ -0,0 +1,32 @@
+package com.oying.config.webConfig;
+
+import org.springframework.boot.web.servlet.MultipartConfigFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import javax.servlet.MultipartConfigElement;
+import java.io.File;
+
+/**
+ * @date 2018-12-28
+ * @author Z
+ */
+@Configuration
+public class MultipartConfig {
+
+    /**
+     * 文件上传临时路径
+     */
+    @Bean
+    MultipartConfigElement multipartConfigElement() {
+        MultipartConfigFactory factory = new MultipartConfigFactory();
+        String location = System.getProperty("user.home") + "/.oying/file/tmp";
+        File tmpFile = new File(location);
+        if (!tmpFile.exists()) {
+            if (!tmpFile.mkdirs()) {
+                System.out.println("create was not successful.");
+            }
+        }
+        factory.setLocation(location);
+        return factory.createMultipartConfig();
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/config/webConfig/QueryCustomizer.java b/oying-common/src/main/java/com/oying/config/webConfig/QueryCustomizer.java
new file mode 100644
index 0000000..9d84b9f
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/webConfig/QueryCustomizer.java
@@ -0,0 +1,18 @@
+package com.oying.config.webConfig;
+
+import org.apache.catalina.connector.Connector;
+import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 允许在查询字符串中使用特定的特殊字符
+ * @author Z
+ */
+@Configuration(proxyBeanMethods = false)
+public class QueryCustomizer implements TomcatConnectorCustomizer {
+
+    @Override
+    public void customize(Connector connector) {
+        connector.setProperty("relaxedQueryChars", "[]{}");
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/config/webConfig/SwaggerConfig.java b/oying-common/src/main/java/com/oying/config/webConfig/SwaggerConfig.java
new file mode 100644
index 0000000..53b8c83
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/webConfig/SwaggerConfig.java
@@ -0,0 +1,152 @@
+package com.oying.config.webConfig;
+
+import lombok.RequiredArgsConstructor;
+import com.oying.utils.AnonTagUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.ApiKey;
+import springfox.documentation.service.AuthorizationScope;
+import springfox.documentation.service.SecurityReference;
+import springfox.documentation.service.SecurityScheme;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spi.service.contexts.SecurityContext;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
+import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * api页面 /doc.html
+ * @author Z
+ * @date 2018-11-23
+ */
+@Configuration
+@EnableSwagger2
+@RequiredArgsConstructor
+public class SwaggerConfig {
+
+    @Value("${server.servlet.context-path:}")
+    private String apiPath;
+
+    @Value("${jwt.header}")
+    private String tokenHeader;
+
+    @Value("${swagger.enabled}")
+    private Boolean enabled;
+
+    private final ApplicationContext applicationContext;
+
+    @Bean
+    public Docket createRestApi() {
+        return new Docket(DocumentationType.SWAGGER_2)
+                .enable(enabled)
+                .pathMapping("/")
+                .apiInfo(apiInfo())
+                .select()
+                .paths(PathSelectors.regex("^(?!/error).*"))
+                .paths(PathSelectors.any())
+                .build()
+                //添加登陆认证
+                .securitySchemes(securitySchemes())
+                .securityContexts(securityContexts());
+    }
+
+    private ApiInfo apiInfo() {
+        return new ApiInfoBuilder()
+                .description("一个简单且易上手的 Spring boot 后台管理框架")
+                .title("OYING 接口文档")
+                .version("1.1")
+                .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<>();
+        securityContexts.add(getContextByPath());
+        return securityContexts;
+    }
+
+    private SecurityContext getContextByPath() {
+        Set<String> urls = AnonTagUtils.getAllAnonymousUrl(applicationContext);
+        urls = urls.stream().filter(url -> !url.equals("/")).collect(Collectors.toSet());
+        String regExp = "^(?!" + apiPath + String.join("|" + apiPath, urls) + ").*$";
+        return SecurityContext.builder()
+                .securityReferences(defaultAuth())
+                .operationSelector(o->o.requestMappingPattern()
+                        // 排除不需要认证的接口
+                        .matches(regExp))
+                .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;
+    }
+
+    /**
+     * 解决Springfox与SpringBoot集成后,WebMvcRequestHandlerProvider和WebFluxRequestHandlerProvider冲突问题
+     * @return /
+     */
+    @Bean
+    @SuppressWarnings({"unchecked","all"})
+    public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
+        return new BeanPostProcessor() {
+
+            @Override
+            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+                if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
+                    customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
+                }
+                return bean;
+            }
+
+            private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
+                List<T> filteredMappings = mappings.stream()
+                        .filter(mapping -> mapping.getPatternParser() == null)
+                        .collect(Collectors.toList());
+                mappings.clear();
+                mappings.addAll(filteredMappings);
+            }
+
+            private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
+                Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
+                if (field != null) {
+                    field.setAccessible(true);
+                    try {
+                        return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
+                    } catch (IllegalAccessException e) {
+                        throw new IllegalStateException("Failed to access handlerMappings field", e);
+                    }
+                }
+                return Collections.emptyList();
+            }
+        };
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/config/webConfig/WebSocketConfig.java b/oying-common/src/main/java/com/oying/config/webConfig/WebSocketConfig.java
new file mode 100644
index 0000000..6a151d5
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/config/webConfig/WebSocketConfig.java
@@ -0,0 +1,18 @@
+package com.oying.config.webConfig;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+/**
+ * @author Z
+ * @date 2019-08-24 15:44
+ */
+@Configuration
+public class WebSocketConfig {
+
+	@Bean
+	public ServerEndpointExporter serverEndpointExporter() {
+		return new ServerEndpointExporter();
+	}
+}
diff --git a/oying-common/src/main/java/com/oying/exception/BadRequestException.java b/oying-common/src/main/java/com/oying/exception/BadRequestException.java
new file mode 100644
index 0000000..30d91f5
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/exception/BadRequestException.java
@@ -0,0 +1,25 @@
+package com.oying.exception;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+
+/**
+ * @author Z
+ * @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();
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/exception/EntityExistException.java b/oying-common/src/main/java/com/oying/exception/EntityExistException.java
new file mode 100644
index 0000000..a960ac0
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/exception/EntityExistException.java
@@ -0,0 +1,19 @@
+package com.oying.exception;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Z
+ * @date 2018-11-23
+ */
+public class EntityExistException extends RuntimeException {
+
+    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)
+                + " with " + field + " "+ val + " existed";
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/exception/EntityNotFoundException.java b/oying-common/src/main/java/com/oying/exception/EntityNotFoundException.java
new file mode 100644
index 0000000..3da3fbd
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/exception/EntityNotFoundException.java
@@ -0,0 +1,19 @@
+package com.oying.exception;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Z
+ * @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";
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/exception/handler/ApiError.java b/oying-common/src/main/java/com/oying/exception/handler/ApiError.java
new file mode 100644
index 0000000..387fca9
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/exception/handler/ApiError.java
@@ -0,0 +1,34 @@
+package com.oying.exception.handler;
+
+import lombok.Data;
+
+/**
+ * @author Z
+ * @date 2018-11-23
+ */
+@Data
+public class ApiError {
+
+    private Integer status = 400;
+    private Long timestamp;
+    private String message;
+
+    private ApiError() {
+        timestamp = System.currentTimeMillis();
+    }
+
+    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;
+    }
+}
+
+
diff --git a/oying-common/src/main/java/com/oying/exception/handler/GlobalExceptionHandler.java b/oying-common/src/main/java/com/oying/exception/handler/GlobalExceptionHandler.java
new file mode 100644
index 0000000..1c92ae1
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/exception/handler/GlobalExceptionHandler.java
@@ -0,0 +1,97 @@
+package com.oying.exception.handler;
+
+import com.oying.exception.EntityExistException;
+import com.oying.exception.EntityNotFoundException;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.exception.BadRequestException;
+import com.oying.utils.ThrowableUtil;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.validation.FieldError;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import static org.springframework.http.HttpStatus.*;
+
+/**
+ * @author Z
+ * @date 2018-11-23
+ */
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    /**
+     * 处理所有不可知的异常
+     */
+    @ExceptionHandler(Throwable.class)
+    public ResponseEntity<ApiError> handleException(Throwable e){
+        // 打印堆栈信息
+        log.error(ThrowableUtil.getStackTrace(e));
+        return buildResponseEntity(ApiError.error(e.getMessage()));
+    }
+
+    /**
+     * BadCredentialsException
+     */
+    @ExceptionHandler(BadCredentialsException.class)
+    public ResponseEntity<ApiError> badCredentialsException(BadCredentialsException e){
+        // 打印堆栈信息
+        String message = "坏的凭证".equals(e.getMessage()) ? "用户名或密码不正确" : e.getMessage();
+        log.error(message);
+        return buildResponseEntity(ApiError.error(message));
+    }
+
+    /**
+     * 处理自定义异常
+     */
+	@ExceptionHandler(value = BadRequestException.class)
+	public ResponseEntity<ApiError> badRequestException(BadRequestException e) {
+        // 打印堆栈信息
+        log.error(ThrowableUtil.getStackTrace(e));
+        return buildResponseEntity(ApiError.error(e.getStatus(),e.getMessage()));
+	}
+
+    /**
+     * 处理 EntityExist
+     */
+    @ExceptionHandler(value = EntityExistException.class)
+    public ResponseEntity<ApiError> entityExistException(EntityExistException e) {
+        // 打印堆栈信息
+        log.error(ThrowableUtil.getStackTrace(e));
+        return buildResponseEntity(ApiError.error(e.getMessage()));
+    }
+
+    /**
+     * 处理 EntityNotFound
+     */
+    @ExceptionHandler(value = EntityNotFoundException.class)
+    public ResponseEntity<ApiError> entityNotFoundException(EntityNotFoundException e) {
+        // 打印堆栈信息
+        log.error(ThrowableUtil.getStackTrace(e));
+        return buildResponseEntity(ApiError.error(NOT_FOUND.value(),e.getMessage()));
+    }
+
+    /**
+     * 处理所有接口数据验证异常
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<ApiError> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
+        // 打印堆栈信息
+        log.error(ThrowableUtil.getStackTrace(e));
+        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
+        String message = objectError.getDefaultMessage();
+        if (objectError instanceof FieldError) {
+            message = ((FieldError) objectError).getField() + ": " + message;
+        }
+        return buildResponseEntity(ApiError.error(message));
+    }
+
+    /**
+     * 统一返回
+     */
+    private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) {
+        return new ResponseEntity<>(apiError, valueOf(apiError.getStatus()));
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/AnonTagUtils.java b/oying-common/src/main/java/com/oying/utils/AnonTagUtils.java
new file mode 100644
index 0000000..7c24628
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/AnonTagUtils.java
@@ -0,0 +1,87 @@
+package com.oying.utils;
+
+import com.oying.annotation.rest.AnonymousAccess;
+import com.oying.utils.enums.RequestMethodEnum;
+import org.springframework.context.ApplicationContext;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+import java.util.*;
+
+/**
+ * @author Z
+ * @description 匿名标记工具
+ * @date 2025-01-13
+ **/
+public class AnonTagUtils {
+
+    /**
+     * 获取匿名标记的URL
+     * @param applicationContext /
+     * @return /
+     */
+    public static Map<String, Set<String>> getAnonymousUrl(ApplicationContext applicationContext){
+        RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
+        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
+        Map<String, Set<String>> anonymousUrls = new HashMap<>(8);
+        // 获取匿名标记
+        Set<String> get = new HashSet<>();
+        Set<String> post = new HashSet<>();
+        Set<String> put = new HashSet<>();
+        Set<String> patch = new HashSet<>();
+        Set<String> delete = new HashSet<>();
+        Set<String> all = new HashSet<>();
+        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
+            HandlerMethod handlerMethod = infoEntry.getValue();
+            AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
+            if (null != anonymousAccess) {
+                List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
+                RequestMethodEnum request = RequestMethodEnum.find(requestMethods.isEmpty() ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
+                if (infoEntry.getKey().getPatternsCondition()!=null) {
+                    switch (Objects.requireNonNull(request)) {
+                        case GET:
+                            get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
+                            break;
+                        case POST:
+                            post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
+                            break;
+                        case PUT:
+                            put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
+                            break;
+                        case PATCH:
+                            patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
+                            break;
+                        case DELETE:
+                            delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
+                            break;
+                        default:
+                            all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
+                            break;
+                    }
+                }
+            }
+        }
+        anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
+        anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
+        anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
+        anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
+        anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
+        anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);
+        return anonymousUrls;
+    }
+
+    /**
+     * 获取所有匿名标记的URL
+     * @param applicationContext /
+     * @return /
+     */
+    public static Set<String> getAllAnonymousUrl(ApplicationContext applicationContext){
+        Set<String> allUrl = new HashSet<>();
+        Map<String, Set<String>> anonymousUrls = getAnonymousUrl(applicationContext);
+        for (String key : anonymousUrls.keySet()) {
+            allUrl.addAll(anonymousUrls.get(key));
+        }
+        return allUrl;
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/BigDecimalUtils.java b/oying-common/src/main/java/com/oying/utils/BigDecimalUtils.java
new file mode 100644
index 0000000..82f029d
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/BigDecimalUtils.java
@@ -0,0 +1,128 @@
+package com.oying.utils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+/**
+ * @author Z
+ * @description 计算类
+ * @date 2024-12-27
+ **/
+public class BigDecimalUtils {
+
+    /**
+     * 将对象转换为 BigDecimal
+     * @param obj 输入对象
+     * @return 转换后的 BigDecimal
+     */
+    private static BigDecimal toBigDecimal(Object obj) {
+        if (obj instanceof BigDecimal) {
+            return (BigDecimal) obj;
+        } else if (obj instanceof Long) {
+            return BigDecimal.valueOf((Long) obj);
+        } else if (obj instanceof Integer) {
+            return BigDecimal.valueOf((Integer) obj);
+        } else if (obj instanceof Double) {
+            return new BigDecimal(String.valueOf(obj));
+        } else {
+            throw new IllegalArgumentException("Unsupported type");
+        }
+    }
+
+    /**
+     * 加法
+     * @param a 加数
+     * @param b 加数
+     * @return 两个加数的和,保留两位小数
+     */
+    public static BigDecimal add(Object a, Object b) {
+        BigDecimal bdA = toBigDecimal(a);
+        BigDecimal bdB = toBigDecimal(b);
+        return bdA.add(bdB).setScale(2, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 减法
+     * @param a 被减数
+     * @param b 减数
+     * @return 两数的差,保留两位小数
+     */
+    public static BigDecimal subtract(Object a, Object b) {
+        BigDecimal bdA = toBigDecimal(a);
+        BigDecimal bdB = toBigDecimal(b);
+        return bdA.subtract(bdB).setScale(2, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 乘法
+     * @param a 乘数
+     * @param b 乘数
+     * @return 两个乘数的积,保留两位小数
+     */
+    public static BigDecimal multiply(Object a, Object b) {
+        BigDecimal bdA = toBigDecimal(a);
+        BigDecimal bdB = toBigDecimal(b);
+        return bdA.multiply(bdB).setScale(2, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 除法
+     * @param a 被除数
+     * @param b 除数
+     * @return 两数的商,保留两位小数
+     */
+    public static BigDecimal divide(Object a, Object b) {
+        BigDecimal bdA = toBigDecimal(a);
+        BigDecimal bdB = toBigDecimal(b);
+        return bdA.divide(bdB, 2, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 除法
+     * @param a 被除数
+     * @param b 除数
+     * @param scale 保留小数位数
+     * @return 两数的商,保留两位小数
+     */
+    public static BigDecimal divide(Object a, Object b, int scale) {
+        BigDecimal bdA = toBigDecimal(a);
+        BigDecimal bdB = toBigDecimal(b);
+        return bdA.divide(bdB, scale, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 分转元
+     * @param obj 分的金额
+     * @return 转换后的元,保留两位小数
+     */
+    public static BigDecimal centsToYuan(Object obj) {
+        BigDecimal cents = toBigDecimal(obj);
+        return cents.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 元转分
+     * @param obj 元的金额
+     * @return 转换后的分
+     */
+    public static Long yuanToCents(Object obj) {
+        BigDecimal yuan = toBigDecimal(obj);
+        return yuan.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).longValue();
+    }
+
+    public static void main(String[] args) {
+        BigDecimal num1 = new BigDecimal("10.123");
+        BigDecimal num2 = new BigDecimal("2.456");
+
+        System.out.println("加法结果: " + add(num1, num2));
+        System.out.println("减法结果: " + subtract(num1, num2));
+        System.out.println("乘法结果: " + multiply(num1, num2));
+        System.out.println("除法结果: " + divide(num1, num2));
+
+        Long cents = 12345L;
+        System.out.println("分转元结果: " + centsToYuan(cents));
+
+        BigDecimal yuan = new BigDecimal("123.45");
+        System.out.println("元转分结果: " + yuanToCents(yuan));
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/CacheKey.java b/oying-common/src/main/java/com/oying/utils/CacheKey.java
new file mode 100644
index 0000000..935c73a
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/CacheKey.java
@@ -0,0 +1,51 @@
+package com.oying.utils;
+
+/**
+ * @author Z
+ * @date 2020/6/11 15:49
+ * @description 关于缓存的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_USER = "role::user:";
+
+    /**
+     * 角色信息
+     */
+    String ROLE_ID = "role::id:";
+
+    /**
+     * 机构
+     */
+    String DEPT_ID = "dept::id:";
+
+    /**
+     * 岗位
+     */
+    String JOB_ID = "job::id:";
+
+    /**
+     * 数据字典
+     */
+    String DICT_NAME = "dict::name:";
+}
diff --git a/oying-common/src/main/java/com/oying/utils/CloseUtil.java b/oying-common/src/main/java/com/oying/utils/CloseUtil.java
new file mode 100644
index 0000000..395951d
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/CloseUtil.java
@@ -0,0 +1,31 @@
+package com.oying.utils;
+
+import java.io.Closeable;
+
+/**
+ * @author Z
+ * @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) {
+                // 静默关闭
+            }
+        }
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/DateUtil.java b/oying-common/src/main/java/com/oying/utils/DateUtil.java
new file mode 100644
index 0000000..5b7ec2e
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/DateUtil.java
@@ -0,0 +1,153 @@
+package com.oying.utils;
+
+import java.sql.Timestamp;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+
+/**
+ * @author Z
+ * @date 2020/6/11 16:28
+ * @description 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");
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 获取当前时间
+     * @return 、
+     */
+    public static Timestamp getTimeStamp() {
+        return Timestamp.valueOf(LocalDateTime.now());
+    }
+
+    /**
+     * 日期格式化 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));
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/ElConstant.java b/oying-common/src/main/java/com/oying/utils/ElConstant.java
new file mode 100644
index 0000000..76e5440
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/ElConstant.java
@@ -0,0 +1,19 @@
+package com.oying.utils;
+
+/**
+ * 常用静态常量
+ *
+ * @author Z
+ * @date 2018-12-26
+ */
+public class ElConstant {
+    /**
+     * win 系统
+     */
+    public static final String WIN = "win";
+
+    /**
+     * mac 系统
+     */
+    public static final String MAC = "mac";
+}
diff --git a/oying-common/src/main/java/com/oying/utils/EncryptUtils.java b/oying-common/src/main/java/com/oying/utils/EncryptUtils.java
new file mode 100644
index 0000000..ea34dd0
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/EncryptUtils.java
@@ -0,0 +1,81 @@
+package com.oying.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 Z
+ * @date 2018-11-23
+ */
+public class EncryptUtils {
+
+    private static final String STR_PARAM = "Passw0rd";
+    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.isEmpty()) {
+            return null;
+        }
+        String strKey = "Passw0rd";
+        return new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8));
+    }
+
+    /**
+     * 对称加密
+     */
+    public static String desEncrypt(String source) throws Exception {
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        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 {
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        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) {
+                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;
+    }
+}
+
diff --git a/oying-common/src/main/java/com/oying/utils/FileUtil.java b/oying-common/src/main/java/com/oying/utils/FileUtil.java
new file mode 100644
index 0000000..db88388
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/FileUtil.java
@@ -0,0 +1,394 @@
+package com.oying.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 com.oying.exception.BadRequestException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.xssf.streaming.SXSSFSheet;
+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.nio.file.Files;
+import java.security.MessageDigest;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * File工具类,扩展 hutool 工具包
+ *
+ * @author Z
+ * @date 2018-12-27
+ */
+@Slf4j
+public class FileUtil extends cn.hutool.core.io.FileUtil {
+
+    /**
+     * 系统临时目录
+     * <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.isEmpty())) {
+            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.isEmpty())) {
+            int dot = filename.lastIndexOf('.');
+            if (dot > -1) {
+                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 = Files.newOutputStream(file.toPath());
+            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) {
+            log.error(e.getMessage(), e);
+        } 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(verifyFilename(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);
+        // 处理数据以防止CSV注入
+        List<Map<String, Object>> sanitizedList = list.parallelStream().map(map -> {
+            Map<String, Object> sanitizedMap = new LinkedHashMap<>();
+            map.forEach((key, value) -> {
+                if (value instanceof String) {
+                    String strValue = (String) value;
+                    // 检查并处理以特殊字符开头的值
+                    if (strValue.startsWith("=") || strValue.startsWith("+") || strValue.startsWith("-") || strValue.startsWith("@")) {
+                        strValue = "'" + strValue; // 添加单引号前缀
+                    }
+                    sanitizedMap.put(key, strValue);
+                } else {
+                    sanitizedMap.put(key, value);
+                }
+            });
+            return sanitizedMap;
+        }).collect(Collectors.toList());
+        // 一次性写出内容,使用默认样式,强制输出标题
+        writer.write(sanitizedList, 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("文件超出规定大小:" + maxSize + "MB");
+        }
+    }
+
+    /**
+     * 判断两个文件是否相同
+     */
+    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 = Files.newInputStream(file.toPath());
+            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);
+                }
+            }
+        }
+    }
+
+    /**
+     * 验证并过滤非法的文件名
+     * @param fileName 文件名
+     * @return 文件名
+     */
+    public static String verifyFilename(String fileName) {
+        // 过滤掉特殊字符
+        fileName = fileName.replaceAll("[\\\\/:*?\"<>|~\\s]", "");
+
+        // 去掉文件名开头和结尾的空格和点
+        fileName = fileName.trim().replaceAll("^[. ]+|[. ]+$", "");
+
+        // 不允许文件名超过255(在Mac和Linux中)或260(在Windows中)个字符
+        int maxFileNameLength = 255;
+        if (System.getProperty("os.name").startsWith("Windows")) {
+            maxFileNameLength = 260;
+        }
+        if (fileName.length() > maxFileNameLength) {
+            fileName = fileName.substring(0, maxFileNameLength);
+        }
+
+        // 过滤掉控制字符
+        fileName = fileName.replaceAll("[\\p{Cntrl}]", "");
+
+        // 过滤掉 ".." 路径
+        fileName = fileName.replaceAll("\\.{2,}", "");
+
+        // 去掉文件名开头的 ".."
+        fileName = fileName.replaceAll("^\\.+/", "");
+
+        // 保留文件名中最后一个 "." 字符,过滤掉其他 "."
+        fileName = fileName.replaceAll("^(.*)(\\.[^.]*)$", "$1").replaceAll("\\.", "") +
+                fileName.replaceAll("^(.*)(\\.[^.]*)$", "$2");
+
+        return fileName;
+    }
+
+    public static String getMd5(File file) {
+        return getMd5(getByte(file));
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/PageResult.java b/oying-common/src/main/java/com/oying/utils/PageResult.java
new file mode 100644
index 0000000..54194ae
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/PageResult.java
@@ -0,0 +1,21 @@
+package com.oying.utils;
+
+import lombok.*;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 分页结果封装类
+ * @author Z
+ * @date 2018-11-23
+ * @param <T>
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class PageResult<T> implements Serializable {
+
+    private List<T> content;
+
+    private long totalElements;
+}
diff --git a/oying-common/src/main/java/com/oying/utils/PageUtil.java b/oying-common/src/main/java/com/oying/utils/PageUtil.java
new file mode 100644
index 0000000..60f0e42
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/PageUtil.java
@@ -0,0 +1,55 @@
+package com.oying.utils;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import java.util.*;
+
+/**
+ * 分页工具
+ * @author Z
+ * @date 2018-12-10
+ */
+public class PageUtil extends cn.hutool.core.util.PageUtil {
+
+    /**
+     * List 分页
+     */
+    public static <T> List<T> paging(int page, int size , List<T> list) {
+        int fromIndex = page * size;
+        int toIndex = page * size + size;
+        if(fromIndex > list.size()){
+            return Collections.emptyList();
+        } else if(toIndex >= list.size()) {
+            return list.subList(fromIndex,list.size());
+        } else {
+            return list.subList(fromIndex,toIndex);
+        }
+    }
+
+    /**
+     * Page 数据处理
+     */
+    public static <T> PageResult<T> toPage(IPage<T> page) {
+        return new PageResult<>(page.getRecords(), page.getTotal());
+    }
+
+    /**
+     * 自定义分页
+     */
+    public static <T> PageResult<T> toPage(List<T> list) {
+        return new PageResult<>(list, list.size());
+    }
+
+    /**
+     * 返回空数据
+     */
+    public static <T> PageResult<T> noData () {
+        return new PageResult<>(null, 0);
+    }
+
+    /**
+     * 自定义分页
+     */
+    public static <T> PageResult<T> toPage(List<T> list, long totalElements) {
+        return new PageResult<>(list, totalElements);
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/RedisUtils.java b/oying-common/src/main/java/com/oying/utils/RedisUtils.java
new file mode 100644
index 0000000..ccd9126
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/RedisUtils.java
@@ -0,0 +1,789 @@
+package com.oying.utils;
+
+import cn.hutool.core.util.StrUtil;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.*;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.springframework.stereotype.Component;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * @author Z
+ */
+@Component
+@SuppressWarnings({"unchecked","all"})
+public class RedisUtils {
+    private static final Logger log = LoggerFactory.getLogger(RedisUtils.class);
+
+    private RedisTemplate<Object, Object> redisTemplate;
+
+    public RedisUtils(RedisTemplate<Object, Object> redisTemplate) {
+        this.redisTemplate = redisTemplate;
+        this.redisTemplate.setKeySerializer(new StringRedisSerializer());
+        this.redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+    }
+
+    /**
+     * 指定缓存失效时间
+     *
+     * @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) {
+                    if (redisTemplate.hasKey(key))
+                        keySet.add(key);
+                }
+                long count = redisTemplate.delete(keySet);
+                log.debug("--------------------------------------------");
+                log.debug("成功删除缓存:" + keySet.toString());
+                log.debug("缓存删除数量:" + count + "个");
+                log.debug("--------------------------------------------");
+            }
+        }
+    }
+
+    /**
+     * 批量模糊删除key
+     * @param pattern
+     */
+    public void scanDel(String pattern){
+        ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
+        try (Cursor<byte[]> cursor = redisTemplate.executeWithStickyConnection(
+                (RedisCallback<Cursor<byte[]>>) connection -> (Cursor<byte[]>) new ConvertingCursor<>(
+                        connection.scan(options), redisTemplate.getKeySerializer()::deserialize))) {
+            while (cursor.hasNext()) {
+                redisTemplate.delete(cursor.next());
+            }
+        }
+    }
+
+    // ============================String=============================
+
+    /**
+     * 普通缓存获取
+     *
+     * @param key 键
+     * @return 值
+     */
+    public Object get(String key) {
+        return key == null ? null : redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 普通缓存获取
+     *
+     * @param key 键
+     * @return 值
+     */
+    public <T> T get(String key, Class<T> clazz) {
+        Object value = key == null ? null : redisTemplate.opsForValue().get(key);
+        if (value == null) {
+            return null;
+        }
+        if (clazz.isInstance(value)) {
+            return clazz.cast(value);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * 普通缓存获取
+     *
+     * @param key 键
+     * @param clazz 列表中元素的类型
+     * @return 值
+     */
+    public <T> List<T> getList(String key, Class<T> clazz) {
+        Object value = key == null ? null : redisTemplate.opsForValue().get(key);
+        if (value == null) {
+            return null;
+        }
+        if (value instanceof List<?>) {
+            List<?> list = (List<?>) value;
+            // 检查每个元素是否为指定类型
+            if (list.stream().allMatch(clazz::isInstance)) {
+                return list.stream().map(clazz::cast).collect(Collectors.toList());
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * 普通缓存获取
+     *
+     * @param key 键
+     * @return 值
+     */
+    public String getStr(String key) {
+        if(StrUtil.isBlank(key)){
+            return null;
+        }
+        Object value = redisTemplate.opsForValue().get(key);
+        if (value == null) {
+            return null;
+        } else {
+            return String.valueOf(value);
+        }
+    }
+
+    /**
+     * 批量获取
+     *
+     * @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) {
+        int attempt = 0;
+        while (attempt < 3) {
+            try {
+                redisTemplate.opsForValue().set(key, value);
+                return true;
+            } catch (Exception e) {
+                attempt++;
+                log.error("Attempt {} failed: {}", attempt, 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 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
+     * @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);
+    }
+
+    // ============================incr=============================
+
+    /**
+     * 递增
+     * @param key
+     * @return
+     */
+    public Long increment(String key) {
+        return redisTemplate.opsForValue().increment(key);
+    }
+
+    /**
+     * 递减
+     * @param key
+     * @return
+     */
+    public Long decrement(String key) {
+        return redisTemplate.opsForValue().decrement(key);
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/RequestHolder.java b/oying-common/src/main/java/com/oying/utils/RequestHolder.java
new file mode 100644
index 0000000..012c2c7
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/RequestHolder.java
@@ -0,0 +1,18 @@
+package com.oying.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 Z
+ * @date 2018-11-24
+ */
+public class RequestHolder {
+
+    public static HttpServletRequest getHttpServletRequest() {
+        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/RsaUtils.java b/oying-common/src/main/java/com/oying/utils/RsaUtils.java
new file mode 100644
index 0000000..2a04f45
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/RsaUtils.java
@@ -0,0 +1,198 @@
+package com.oying.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 Z
+ * @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;
+        }
+
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/SecurityUtils.java b/oying-common/src/main/java/com/oying/utils/SecurityUtils.java
new file mode 100644
index 0000000..f6fc90d
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/SecurityUtils.java
@@ -0,0 +1,129 @@
+package com.oying.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.jwt.JWT;
+import cn.hutool.jwt.JWTUtil;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.utils.enums.DataScopeEnum;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 获取当前登录的用户
+ * @author Z
+ * @date 2019-01-17
+ */
+@Slf4j
+@Component
+public class SecurityUtils {
+
+    public static String header;
+
+    public static String tokenStartWith;
+
+    @Value("${jwt.header}")
+    public void setHeader(String header) {
+        SecurityUtils.header = header;
+    }
+
+    @Value("${jwt.token-start-with}")
+    public void setTokenStartWith(String tokenStartWith) {
+        SecurityUtils.tokenStartWith = tokenStartWith;
+    }
+
+    /**
+     * 获取当前登录的用户
+     * @return UserDetails
+     */
+    public static UserDetails getCurrentUser() {
+        UserDetailsService userDetailsService = SpringBeanHolder.getBean(UserDetailsService.class);
+        return userDetailsService.loadUserByUsername(getCurrentUsername());
+    }
+
+    /**
+     * 获取当前用户的数据权限
+     * @return /
+     */
+    public static List<Long> getCurrentUserDataScope(){
+        UserDetails userDetails = getCurrentUser();
+        // 将 Java 对象转换为 JSONObject 对象
+        JSONObject jsonObject = (JSONObject) JSON.toJSON(userDetails);
+        JSONArray jsonArray = jsonObject.getJSONArray("dataScopes");
+        return JSON.parseArray(jsonArray.toJSONString(), Long.class);
+    }
+
+    /**
+     * 获取数据权限级别
+     * @return 级别
+     */
+    public static String getDataScopeType() {
+        List<Long> dataScopes = getCurrentUserDataScope();
+        if(CollUtil.isEmpty(dataScopes)){
+            return "";
+        }
+        return DataScopeEnum.ALL.getValue();
+    }
+
+    /**
+     * 获取用户ID
+     * @return 系统用户ID
+     */
+    public static Long getCurrentUserId() {
+        return getCurrentUserId(getToken());
+    }
+
+    /**
+     * 获取用户ID
+     * @return 系统用户ID
+     */
+    public static Long getCurrentUserId(String token) {
+        JWT jwt = JWTUtil.parseToken(token);
+        return Long.valueOf(jwt.getPayload("userId").toString());
+    }
+
+    /**
+     * 获取系统用户名称
+     *
+     * @return 系统用户名称
+     */
+    public static String getCurrentUsername() {
+        return getCurrentUsername(getToken());
+    }
+
+    /**
+     * 获取系统用户名称
+     *
+     * @return 系统用户名称
+     */
+    public static String getCurrentUsername(String token) {
+        JWT jwt = JWTUtil.parseToken(token);
+        return jwt.getPayload("sub").toString();
+    }
+
+    /**
+     * 获取Token
+     * @return /
+     */
+    public static String getToken() {
+        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder
+                .getRequestAttributes())).getRequest();
+        String bearerToken = request.getHeader(header);
+        if (bearerToken != null && bearerToken.startsWith(tokenStartWith)) {
+            // 去掉令牌前缀
+            return bearerToken.replace(tokenStartWith, "");
+        } else {
+            log.debug("非法Token:{}", bearerToken);
+        }
+        return null;
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/SpringBeanHolder.java b/oying-common/src/main/java/com/oying/utils/SpringBeanHolder.java
new file mode 100644
index 0000000..e022e07
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/SpringBeanHolder.java
@@ -0,0 +1,157 @@
+package com.oying.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 org.springframework.stereotype.Service;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Z
+ * @date 2019-01-07
+ */
+@Slf4j
+@SuppressWarnings({"unchecked","all"})
+public class SpringBeanHolder 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) {
+            SpringBeanHolder.CALL_BACKS.add(callBack);
+        } else {
+            log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName());
+            callBack.executor();
+        }
+    }
+
+    /**
+     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+     */
+    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() {
+        SpringBeanHolder.clearHolder();
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        if (SpringBeanHolder.applicationContext != null) {
+            log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringBeanHolder.applicationContext);
+        }
+        SpringBeanHolder.applicationContext = applicationContext;
+        if (addCallback) {
+            for (CallBack callBack : SpringBeanHolder.CALL_BACKS) {
+                callBack.executor();
+            }
+            CALL_BACKS.clear();
+        }
+        SpringBeanHolder.addCallback = false;
+    }
+
+    /**
+     * 获取 @Service 的所有 bean 名称
+     * @return /
+     */
+    public static List<String> getAllServiceBeanName() {
+        return new ArrayList<>(Arrays.asList(applicationContext
+                .getBeanNamesForAnnotation(Service.class)));
+    }
+
+    interface CallBack {
+
+        /**
+         * 回调执行方法
+         */
+        void executor();
+
+        /**
+         * 本回调任务名称
+         * @return /
+         */
+        default String getCallBackName() {
+            return Thread.currentThread().getId() + ":" + this.getClass().getName();
+        }
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/StringUtils.java b/oying-common/src/main/java/com/oying/utils/StringUtils.java
new file mode 100644
index 0000000..a7d385e
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/StringUtils.java
@@ -0,0 +1,228 @@
+package com.oying.utils;
+
+import cn.hutool.http.useragent.UserAgent;
+import cn.hutool.http.useragent.UserAgentUtil;
+import lombok.extern.slf4j.Slf4j;
+import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
+import net.dreamlu.mica.ip2region.core.IpInfo;
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.UnknownHostException;
+import java.util.*;
+
+/**
+ * @author Z
+ * 字符串工具类, 继承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 = SpringBeanHolder.getBean(Ip2regionSearcher.class);
+
+    /**
+     * 驼峰命名法工具
+     *
+     * @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.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.isEmpty() || 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) {
+        IpInfo ipInfo = IP_SEARCHER.memorySearch(ip);
+        if(ipInfo != null){
+            return ipInfo.getAddress();
+        }
+        return null;
+    }
+
+    /**
+     * 获取浏览器
+     */
+    public static String getBrowser(HttpServletRequest request) {
+        UserAgent ua = UserAgentUtil.parse(request.getHeader("User-Agent"));
+        String browser = ua.getBrowser().toString() + " " + ua.getVersion();
+        return browser.replace(".0.0.0","");
+    }
+
+    /**
+     * 获得当天是周几
+     */
+    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 "";
+        }
+    }
+
+    @SuppressWarnings({"unchecked","all"})
+    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;
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/ThrowableUtil.java b/oying-common/src/main/java/com/oying/utils/ThrowableUtil.java
new file mode 100644
index 0000000..eef15e5
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/ThrowableUtil.java
@@ -0,0 +1,22 @@
+package com.oying.utils;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * 异常工具 2019-01-06
+ * @author Z
+ */
+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();
+        }
+    }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/enums/CodeBiEnum.java b/oying-common/src/main/java/com/oying/utils/enums/CodeBiEnum.java
new file mode 100644
index 0000000..36ad3f8
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/enums/CodeBiEnum.java
@@ -0,0 +1,35 @@
+package com.oying.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * <p>
+ * 验证码业务场景
+ * </p>
+ * @author Z
+ * @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 (value.getCode().equals(code)) {
+                return value;
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/oying-common/src/main/java/com/oying/utils/enums/CodeEnum.java b/oying-common/src/main/java/com/oying/utils/enums/CodeEnum.java
new file mode 100644
index 0000000..f6e7d08
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/enums/CodeEnum.java
@@ -0,0 +1,31 @@
+package com.oying.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * <p>
+ * 验证码业务场景对应的 Redis 中的 key
+ * </p>
+ * @author Z
+ * @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;
+}
diff --git a/oying-common/src/main/java/com/oying/utils/enums/DataScopeEnum.java b/oying-common/src/main/java/com/oying/utils/enums/DataScopeEnum.java
new file mode 100644
index 0000000..af88297
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/enums/DataScopeEnum.java
@@ -0,0 +1,38 @@
+package com.oying.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * <p>
+ * 数据权限枚举
+ * </p>
+ * @author Z
+ * @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 (dataScopeEnum.getValue().equals(val)) {
+                return dataScopeEnum;
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/oying-common/src/main/java/com/oying/utils/enums/RequestMethodEnum.java b/oying-common/src/main/java/com/oying/utils/enums/RequestMethodEnum.java
new file mode 100644
index 0000000..1af6207
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/enums/RequestMethodEnum.java
@@ -0,0 +1,58 @@
+package com.oying.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author Z
+ * @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 (value.getType().equals(type)) {
+                return value;
+            }
+        }
+        return ALL;
+    }
+}
diff --git a/oying-generator/pom.xml b/oying-generator/pom.xml
new file mode 100644
index 0000000..c264b37
--- /dev/null
+++ b/oying-generator/pom.xml
@@ -0,0 +1,37 @@
+<?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>oying</artifactId>
+        <groupId>com.oying</groupId>
+        <version>1.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>oying-generator</artifactId>
+    <name>代码生成模块</name>
+
+    <properties>
+        <configuration.version>1.10</configuration.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.oying</groupId>
+            <artifactId>oying-common</artifactId>
+            <version>1.1</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>
diff --git a/oying-generator/src/main/java/com/oying/domain/ColumnInfo.java b/oying-generator/src/main/java/com/oying/domain/ColumnInfo.java
new file mode 100644
index 0000000..5a4d1c1
--- /dev/null
+++ b/oying-generator/src/main/java/com/oying/domain/ColumnInfo.java
@@ -0,0 +1,62 @@
+package com.oying.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import java.io.Serializable;
+
+/**
+ * 列的数据信息
+ * @author Z
+ * @date 2019-01-02
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@TableName("code_column")
+public class ColumnInfo implements Serializable {
+
+    @ApiModelProperty(value = "ID", hidden = true)
+    @TableId(value = "column_id", type = IdType.AUTO)
+    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 = true;
+
+    @ApiModelProperty(value = "是否表单显示")
+    private Boolean formShow = true;
+
+    @ApiModelProperty(value = "表单类型")
+    private String formType;
+
+    @ApiModelProperty(value = "查询 1:模糊 2:精确")
+    private String queryType;
+
+    @ApiModelProperty(value = "字典名称")
+    private String dictName;
+}
diff --git a/oying-generator/src/main/java/com/oying/domain/GenConfig.java b/oying-generator/src/main/java/com/oying/domain/GenConfig.java
new file mode 100644
index 0000000..4c08c9b
--- /dev/null
+++ b/oying-generator/src/main/java/com/oying/domain/GenConfig.java
@@ -0,0 +1,63 @@
+package com.oying.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+/**
+ * 代码生成配置
+ * @author Z
+ * @date 2019-01-03
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@TableName("code_config")
+public class GenConfig implements Serializable {
+
+    public GenConfig(String tableName) {
+        this.tableName = tableName;
+    }
+
+    @ApiModelProperty(value = "ID", hidden = true)
+    @TableId(value = "config_id", type = IdType.AUTO)
+    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;
+}
diff --git a/oying-generator/src/main/java/com/oying/domain/dto/TableInfo.java b/oying-generator/src/main/java/com/oying/domain/dto/TableInfo.java
new file mode 100644
index 0000000..4f1c9ad
--- /dev/null
+++ b/oying-generator/src/main/java/com/oying/domain/dto/TableInfo.java
@@ -0,0 +1,34 @@
+package com.oying.domain.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 表的数据信息
+ * @author Z
+ * @date 2019-01-02
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class TableInfo {
+
+    @ApiModelProperty(value = "表名称")
+    private Object tableName;
+
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "创建日期:yyyy-MM-dd HH:mm:ss")
+    private Object createTime;
+
+    @ApiModelProperty(value = "数据库引擎")
+    private Object engine;
+
+    @ApiModelProperty(value = "编码集")
+    private Object coding;
+
+    @ApiModelProperty(value = "备注")
+    private Object remark;
+}
diff --git a/oying-generator/src/main/java/com/oying/mapper/ColumnInfoMapper.java b/oying-generator/src/main/java/com/oying/mapper/ColumnInfoMapper.java
new file mode 100644
index 0000000..fec384e
--- /dev/null
+++ b/oying-generator/src/main/java/com/oying/mapper/ColumnInfoMapper.java
@@ -0,0 +1,24 @@
+package com.oying.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.domain.ColumnInfo;
+import com.oying.domain.dto.TableInfo;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+/**
+ * @author Z
+ * @date 2023-06-26
+ */
+@Mapper
+public interface ColumnInfoMapper extends BaseMapper<ColumnInfo> {
+
+    IPage<TableInfo> getTables(@Param("tableName") String tableName, Page<Object> page);
+
+    List<ColumnInfo> findByTableNameOrderByIdAsc(@Param("tableName") String tableName);
+
+    List<ColumnInfo> getColumns(@Param("tableName") String tableName);
+}
diff --git a/oying-generator/src/main/java/com/oying/mapper/GenConfigMapper.java b/oying-generator/src/main/java/com/oying/mapper/GenConfigMapper.java
new file mode 100644
index 0000000..28781e7
--- /dev/null
+++ b/oying-generator/src/main/java/com/oying/mapper/GenConfigMapper.java
@@ -0,0 +1,16 @@
+package com.oying.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.oying.domain.GenConfig;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * @author Z
+ * @date 2023-06-26
+ */
+@Mapper
+public interface GenConfigMapper extends BaseMapper<GenConfig> {
+
+    GenConfig findByTableName(@Param("tableName") String tableName);
+}
diff --git a/oying-generator/src/main/java/com/oying/rest/GenConfigController.java b/oying-generator/src/main/java/com/oying/rest/GenConfigController.java
new file mode 100644
index 0000000..65a788e
--- /dev/null
+++ b/oying-generator/src/main/java/com/oying/rest/GenConfigController.java
@@ -0,0 +1,36 @@
+package com.oying.rest;
+
+import com.oying.domain.GenConfig;
+import com.oying.service.GenConfigService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author Z
+ * @date 2019-01-14
+ */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/genConfig")
+@Api(tags = "系统:代码生成器配置管理")
+public class GenConfigController {
+
+    private final GenConfigService genConfigService;
+
+    @ApiOperation("查询")
+    @GetMapping(value = "/{tableName}")
+    public ResponseEntity<GenConfig> queryGenConfig(@PathVariable String tableName){
+        return new ResponseEntity<>(genConfigService.find(tableName), HttpStatus.OK);
+    }
+
+    @PutMapping
+    @ApiOperation("修改")
+    public ResponseEntity<GenConfig> updateGenConfig(@Validated @RequestBody GenConfig genConfig){
+        return new ResponseEntity<>(genConfigService.update(genConfig.getTableName(), genConfig),HttpStatus.OK);
+    }
+}
diff --git a/oying-generator/src/main/java/com/oying/rest/GeneratorController.java b/oying-generator/src/main/java/com/oying/rest/GeneratorController.java
new file mode 100644
index 0000000..6435ace
--- /dev/null
+++ b/oying-generator/src/main/java/com/oying/rest/GeneratorController.java
@@ -0,0 +1,86 @@
+package com.oying.rest;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.domain.ColumnInfo;
+import com.oying.domain.dto.TableInfo;
+import com.oying.service.GenConfigService;
+import com.oying.service.GeneratorService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import com.oying.exception.BadRequestException;
+import com.oying.utils.PageResult;
+import com.oying.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 Z
+ * @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")
+    public ResponseEntity<PageResult<TableInfo>> queryTables(@RequestParam(defaultValue = "") String name, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size){
+        return new ResponseEntity<>(generatorService.getTables(name, new Page<>(page, size)), HttpStatus.OK);
+    }
+
+    @ApiOperation("查询字段数据")
+    @GetMapping(value = "/columns")
+    public ResponseEntity<PageResult<ColumnInfo>> queryColumns(@RequestParam String tableName){
+        List<ColumnInfo> columnInfos = generatorService.getColumns(tableName);
+        return new ResponseEntity<>(PageUtil.toPage(columnInfos), 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);
+    }
+}
diff --git a/oying-generator/src/main/java/com/oying/service/GenConfigService.java b/oying-generator/src/main/java/com/oying/service/GenConfigService.java
new file mode 100644
index 0000000..f4b8b6c
--- /dev/null
+++ b/oying-generator/src/main/java/com/oying/service/GenConfigService.java
@@ -0,0 +1,26 @@
+package com.oying.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.domain.GenConfig;
+
+/**
+ * @author Z
+ * @date 2019-01-14
+ */
+public interface GenConfigService extends IService<GenConfig> {
+
+    /**
+     * 查询表配置
+     * @param tableName 表名
+     * @return 表配置
+     */
+    GenConfig find(String tableName);
+
+    /**
+     * 更新表配置
+     * @param tableName 表名
+     * @param genConfig 表配置
+     * @return 表配置
+     */
+    GenConfig update(String tableName, GenConfig genConfig);
+}
diff --git a/oying-generator/src/main/java/com/oying/service/GeneratorService.java b/oying-generator/src/main/java/com/oying/service/GeneratorService.java
new file mode 100644
index 0000000..90de3b9
--- /dev/null
+++ b/oying-generator/src/main/java/com/oying/service/GeneratorService.java
@@ -0,0 +1,79 @@
+package com.oying.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.domain.GenConfig;
+import com.oying.domain.ColumnInfo;
+import com.oying.domain.dto.TableInfo;
+import com.oying.utils.PageResult;
+import org.springframework.http.ResponseEntity;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * @author Z
+ * @date 2019-01-02
+ */
+public interface GeneratorService extends IService<ColumnInfo> {
+
+    /**
+     * 查询数据库元数据
+     *
+     * @param name 表名
+     * @param page 分页参数
+     * @return /
+     */
+    PageResult<TableInfo> getTables(String name, Page<Object> page);
+
+    /**
+     * 得到数据表的元数据
+     * @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);
+
+    /**
+     * 代码生成
+     * @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);
+}
diff --git a/oying-generator/src/main/java/com/oying/service/impl/GenConfigServiceImpl.java b/oying-generator/src/main/java/com/oying/service/impl/GenConfigServiceImpl.java
new file mode 100644
index 0000000..0511cce
--- /dev/null
+++ b/oying-generator/src/main/java/com/oying/service/impl/GenConfigServiceImpl.java
@@ -0,0 +1,54 @@
+package com.oying.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import com.oying.domain.GenConfig;
+import com.oying.mapper.GenConfigMapper;
+import com.oying.service.GenConfigService;
+import org.springframework.stereotype.Service;
+import java.io.File;
+
+/**
+ * @author Z
+ * @date 2019-01-14
+ */
+@Service
+@RequiredArgsConstructor
+@SuppressWarnings({"unchecked","all"})
+public class GenConfigServiceImpl extends ServiceImpl<GenConfigMapper, GenConfig> implements GenConfigService {
+
+    private final GenConfigMapper genConfigMapper;
+
+    @Override
+    public GenConfig find(String tableName) {
+        GenConfig genConfig = genConfigMapper.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());
+        saveOrUpdate(genConfig);
+        return genConfig;
+    }
+}
diff --git a/oying-generator/src/main/java/com/oying/service/impl/GeneratorServiceImpl.java b/oying-generator/src/main/java/com/oying/service/impl/GeneratorServiceImpl.java
new file mode 100644
index 0000000..6827edb
--- /dev/null
+++ b/oying-generator/src/main/java/com/oying/service/impl/GeneratorServiceImpl.java
@@ -0,0 +1,150 @@
+package com.oying.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ZipUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.utils.FileUtil;
+import com.oying.utils.PageResult;
+import com.oying.utils.PageUtil;
+import com.oying.utils.StringUtils;
+import com.oying.domain.GenConfig;
+import com.oying.domain.ColumnInfo;
+import com.oying.domain.dto.TableInfo;
+import com.oying.exception.BadRequestException;
+import com.oying.mapper.ColumnInfoMapper;
+import com.oying.service.GeneratorService;
+import com.oying.utils.GenUtil;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Z
+ * @date 2019-01-02
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class GeneratorServiceImpl extends ServiceImpl<ColumnInfoMapper, ColumnInfo> implements GeneratorService {
+
+    private final ColumnInfoMapper columnInfoMapper;
+    private final String CONFIG_MESSAGE = "请先配置生成器";
+
+    @Override
+    public PageResult<TableInfo> getTables(String name, Page<Object> page) {
+        return PageUtil.toPage(columnInfoMapper.getTables(name, page));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public List<ColumnInfo> getColumns(String tableName) {
+        List<ColumnInfo> columnInfos = columnInfoMapper.findByTableNameOrderByIdAsc(tableName);
+        if (CollectionUtil.isNotEmpty(columnInfos)) {
+            return columnInfos;
+        } else {
+            columnInfos = query(tableName);
+            saveBatch(columnInfos);
+            return columnInfos;
+        }
+    }
+
+    @Override
+    public List<ColumnInfo> query(String tableName) {
+        List<ColumnInfo> columnInfos = columnInfoMapper.getColumns(tableName);
+        for (ColumnInfo columnInfo : columnInfos) {
+            columnInfo.setTableName(tableName);
+            if(GenUtil.PK.equalsIgnoreCase(columnInfo.getKeyType())
+                    && GenUtil.EXTRA.equalsIgnoreCase(columnInfo.getExtra())){
+                columnInfo.setNotNull(false);
+            }
+        }
+        return columnInfos;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    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());
+                }
+                saveOrUpdate(column);
+            } else {
+                // 如果找不到,则保存新字段信息
+                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)) {
+                removeById(columnInfo);
+            }
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void save(List<ColumnInfo> columnInfos) {
+        saveOrUpdateBatch(columnInfos);
+    }
+
+    @Override
+    public void generator(GenConfig genConfig, List<ColumnInfo> columns) {
+        if (genConfig.getId() == null) {
+            throw new BadRequestException(CONFIG_MESSAGE);
+        }
+        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(CONFIG_MESSAGE);
+        }
+        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(CONFIG_MESSAGE);
+        }
+        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("打包失败");
+        }
+    }
+}
diff --git a/oying-generator/src/main/java/com/oying/utils/ColUtil.java b/oying-generator/src/main/java/com/oying/utils/ColUtil.java
new file mode 100644
index 0000000..22a2203
--- /dev/null
+++ b/oying-generator/src/main/java/com/oying/utils/ColUtil.java
@@ -0,0 +1,39 @@
+package com.oying.utils;
+
+import org.apache.commons.configuration.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * sql字段转java
+ *
+ * @author Z
+ * @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("gen.properties");
+        } catch (ConfigurationException e) {
+            log.error(e.getMessage(), e);
+        }
+        return null;
+    }
+}
diff --git a/oying-generator/src/main/java/com/oying/utils/GenUtil.java b/oying-generator/src/main/java/com/oying/utils/GenUtil.java
new file mode 100644
index 0000000..07d4a12
--- /dev/null
+++ b/oying-generator/src/main/java/com/oying/utils/GenUtil.java
@@ -0,0 +1,402 @@
+package com.oying.utils;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.template.*;
+import com.oying.domain.GenConfig;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.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 com.oying.utils.FileUtil.SYS_TEM_DIR;
+
+/**
+ * 代码生成
+ *
+ * @author Z
+ * @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("Controller");
+        templateNames.add("QueryCriteria");
+        templateNames.add("Service");
+        templateNames.add("ServiceImpl");
+        templateNames.add("Mapper");
+        templateNames.add("Mapper-xml");
+        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("admin/" + templateName + ".ftl");
+            map.put("content", template.render(genMap));
+            map.put("name", templateName.replace("-xml", ".xml"));
+            genList.add(map);
+        }
+        // 获取前端模版
+        templates = getFrontTemplateNames();
+        for (String templateName : templates) {
+            Map<String, Object> map = new HashMap<>(1);
+            Template template = engine.getTemplate("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 {
+        // 拼接的路径:/tmpoying-gen-temp/,这个路径在Linux下需要root用户才有权限创建,非root用户会权限错误而失败,更改为: /tmp/oying-gen-temp/
+        // String tempPath =SYS_TEM_DIR + "oying-gen-temp" + File.separator + genConfig.getTableName() + File.separator;
+        String tempPath = SYS_TEM_DIR + "oying-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("admin/" + templateName + ".ftl");
+            String filePath = getAdminFilePath(templateName, genConfig, genMap.get("className").toString(), tempPath + "oying" + 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("front/" + templateName + ".ftl");
+            String path = tempPath + "oying-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("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("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);
+        // 存储主键字段名
+        genMap.put("pkIdName", "none");
+        // 存储符号
+        genMap.put("symbol", "#");
+        // 保存字段信息
+        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);
+                // 存储主键字段名
+                genMap.put("pkIdName", column.getColumnName());
+            }
+            // 是否存在 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);
+                if(!dicts.contains(column.getDictName()))
+                    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());
+            // 添加非空字段信息
+            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;
+        String mpXmlPath = projectPath + File.separator + "src" + File.separator + "main" + File.separator + "resources" + 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 ("QueryCriteria".equals(templateName)) {
+            return packagePath + "domain" + File.separator + "dto" + File.separator + className + "QueryCriteria.java";
+        }
+
+        if ("Mapper".equals(templateName)) {
+            return packagePath + "mapper" + File.separator + className + "Mapper.java";
+        }
+
+        if ("Mapper-xml".equals(templateName)) {
+            return mpXmlPath + "mapper" + File.separator + className + "Mapper.xml";
+        }
+
+        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();
+        }
+    }
+}
diff --git a/oying-generator/src/main/resources/gen.properties b/oying-generator/src/main/resources/gen.properties
new file mode 100644
index 0000000..2ed9370
--- /dev/null
+++ b/oying-generator/src/main/resources/gen.properties
@@ -0,0 +1,27 @@
+#数据库类型转Java类型
+tinyint=Integer
+smallint=Integer
+mediumint=Integer
+int=Integer
+integer=Integer
+
+bigint=Long
+
+float=Float
+
+double=Double
+
+decimal=BigDecimal
+
+bit=Boolean
+
+char=String
+varchar=String
+tinytext=String
+text=String
+mediumtext=String
+longtext=String
+
+date=Timestamp
+datetime=Timestamp
+timestamp=Timestamp
\ No newline at end of file
diff --git a/oying-generator/src/main/resources/mapper/ColumnInfoMapper.xml b/oying-generator/src/main/resources/mapper/ColumnInfoMapper.xml
new file mode 100644
index 0000000..5adad43
--- /dev/null
+++ b/oying-generator/src/main/resources/mapper/ColumnInfoMapper.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.mapper.ColumnInfoMapper">
+    <resultMap id="BaseResultMap" type="com.oying.domain.ColumnInfo">
+        <id column="column_id" property="id"/>
+        <result column="table_name" property="tableName"/>
+        <result column="column_name" property="columnName"/>
+        <result column="column_type" property="columnType"/>
+        <result column="key_type" property="keyType"/>
+        <result column="extra" property="extra"/>
+        <result column="remark" property="remark"/>
+        <result column="not_null" property="notNull"/>
+        <result column="list_show" property="listShow"/>
+        <result column="form_show" property="formShow"/>
+        <result column="form_type" property="formType"/>
+        <result column="query_type" property="queryType"/>
+        <result column="dict_name" property="dictName"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        column_id, table_name, column_name, column_type, key_type, extra, remark, not_null, list_show, form_show, form_type, query_type, dict_name
+    </sql>
+
+    <select id="getTables" resultType="com.oying.domain.dto.TableInfo">
+        select table_name, create_time, engine, table_collation as coding, table_comment as remark
+        from information_schema.tables
+        where table_schema = (select database())
+        and table_name like concat('%',#{tableName},'%')
+        order by create_time desc
+    </select>
+
+    <select id="findByTableNameOrderByIdAsc" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List"/>
+        from code_column
+        where table_name = #{tableName}
+        order by column_id
+    </select>
+
+    <select id="getColumns" resultMap="BaseResultMap">
+        select column_name, if(is_nullable = 'NO', 1, 0) not_null,
+               data_type as column_type, column_comment as remark,
+               column_key key_type, extra
+        from information_schema.columns
+        where table_name = #{tableName}
+        and table_schema = (select database())
+        order by ordinal_position
+    </select>
+</mapper>
diff --git a/oying-generator/src/main/resources/mapper/GenConfigMapper.xml b/oying-generator/src/main/resources/mapper/GenConfigMapper.xml
new file mode 100644
index 0000000..6fa557e
--- /dev/null
+++ b/oying-generator/src/main/resources/mapper/GenConfigMapper.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.mapper.GenConfigMapper">
+    <resultMap id="BaseResultMap" type="com.oying.domain.GenConfig">
+        <id column="config_id" property="id"/>
+        <result column="table_name" property="tableName"/>
+        <result column="api_alias" property="apiAlias"/>
+        <result column="pack" property="pack"/>
+        <result column="module_name" property="moduleName"/>
+        <result column="path" property="path"/>
+        <result column="api_path" property="apiPath"/>
+        <result column="author" property="author"/>
+        <result column="prefix" property="prefix"/>
+        <result column="cover" property="cover"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        config_id, table_name, api_alias, pack, module_name, path, api_path, author, prefix, cover
+    </sql>
+
+    <select id="findByTableName" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List"/>
+        FROM code_config
+        WHERE table_name = #{tableName}
+    </select>
+</mapper>
diff --git a/oying-generator/src/main/resources/template/admin/Controller.ftl b/oying-generator/src/main/resources/template/admin/Controller.ftl
new file mode 100644
index 0000000..e62805e
--- /dev/null
+++ b/oying-generator/src/main/resources/template/admin/Controller.ftl
@@ -0,0 +1,73 @@
+package ${package}.rest;
+
+import com.oying.annotation.Log;
+import ${package}.domain.${className};
+import ${package}.service.${className}Service;
+import ${package}.domain.dto.${className}QueryCriteria;
+import lombok.RequiredArgsConstructor;
+import java.util.List;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import io.swagger.annotations.*;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.utils.PageResult;
+
+/**
+* @author ${author}
+* @date ${date}
+**/
+@RestController
+@RequiredArgsConstructor
+@Api(tags = "${apiAlias}")
+@RequestMapping("/api/${changeClassName}")
+public class ${className}Controller {
+
+    private final ${className}Service ${changeClassName}Service;
+
+    @ApiOperation("导出数据")
+    @GetMapping(value = "/download")
+    @PreAuthorize("@el.check('${changeClassName}:list')")
+    public void export${className}(HttpServletResponse response, ${className}QueryCriteria criteria) throws IOException {
+        ${changeClassName}Service.download(${changeClassName}Service.queryAll(criteria), response);
+    }
+
+    @GetMapping
+    @ApiOperation("查询${apiAlias}")
+    @PreAuthorize("@el.check('${changeClassName}:list')")
+    public ResponseEntity<PageResult<${className}>> query${className}(${className}QueryCriteria criteria){
+        Page<Object> page = new Page<>(criteria.getPage(), criteria.getSize());
+        return new ResponseEntity<>(${changeClassName}Service.queryAll(criteria,page),HttpStatus.OK);
+    }
+
+    @PostMapping
+    @Log("新增${apiAlias}")
+    @ApiOperation("新增${apiAlias}")
+    @PreAuthorize("@el.check('${changeClassName}:add')")
+    public ResponseEntity<Object> create${className}(@Validated @RequestBody ${className} resources){
+        ${changeClassName}Service.create(resources);
+        return new ResponseEntity<>(HttpStatus.CREATED);
+    }
+
+    @PutMapping
+    @Log("修改${apiAlias}")
+    @ApiOperation("修改${apiAlias}")
+    @PreAuthorize("@el.check('${changeClassName}:edit')")
+    public ResponseEntity<Object> update${className}(@Validated @RequestBody ${className} resources){
+        ${changeClassName}Service.update(resources);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @DeleteMapping
+    @Log("删除${apiAlias}")
+    @ApiOperation("删除${apiAlias}")
+    @PreAuthorize("@el.check('${changeClassName}:del')")
+    public ResponseEntity<Object> delete${className}(@ApiParam(value = "传ID数组[]") @RequestBody List<${pkColumnType}> ids) {
+        ${changeClassName}Service.deleteAll(ids);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}
diff --git a/oying-generator/src/main/resources/template/admin/Entity.ftl b/oying-generator/src/main/resources/template/admin/Entity.ftl
new file mode 100644
index 0000000..d2e9ac4
--- /dev/null
+++ b/oying-generator/src/main/resources/template/admin/Entity.ftl
@@ -0,0 +1,72 @@
+package ${package}.domain;
+
+import lombok.Data;
+import cn.hutool.core.bean.BeanUtil;
+import io.swagger.annotations.ApiModelProperty;
+import cn.hutool.core.bean.copier.CopyOptions;
+<#if hasTimestamp>
+import java.sql.Timestamp;
+</#if>
+<#if hasBigDecimal>
+import java.math.BigDecimal;
+</#if>
+<#assign notBlankUsed = false>
+<#assign notNullUsed = false>
+<#if columns??>
+    <#list columns as column>
+        <#if column.istNotNull && column.columnKey != 'PRI'>
+            <#if column.columnType = 'String'>
+                <#assign notBlankUsed = true>
+            <#else>
+                <#assign notNullUsed = true>
+            </#if>
+        </#if>
+    </#list>
+</#if>
+<#if notBlankUsed>
+import javax.validation.constraints.NotBlank;
+</#if>
+<#if notNullUsed>
+import javax.validation.constraints.NotNull;
+</#if>
+import java.io.Serializable;
+<#if auto>
+import com.baomidou.mybatisplus.annotation.IdType;
+</#if>
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+/**
+* @description /
+* @author ${author}
+* @date ${date}
+**/
+@Data
+@TableName("${tableName}")
+public class ${className} implements Serializable {
+<#if columns??>
+    <#list columns as column>
+
+    <#if column.columnKey = 'PRI'>
+    @TableId(value = "${column.columnName}"<#if auto>, type = IdType.AUTO</#if>)
+    </#if>
+    <#if column.istNotNull && column.columnKey != 'PRI'>
+        <#if column.columnType = 'String'>
+    @NotBlank
+        <#else>
+    @NotNull
+        </#if>
+    </#if>
+    <#if column.remark != ''>
+    @ApiModelProperty(value = "${column.remark}")
+    <#else>
+    @ApiModelProperty(value = "${column.changeColumnName}")
+    </#if>
+    private ${column.columnType} ${column.changeColumnName};
+    </#list>
+</#if>
+
+    public void copy(${className} source){
+        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
+    }
+}
diff --git a/oying-generator/src/main/resources/template/admin/Mapper-xml.ftl b/oying-generator/src/main/resources/template/admin/Mapper-xml.ftl
new file mode 100644
index 0000000..82bad8d
--- /dev/null
+++ b/oying-generator/src/main/resources/template/admin/Mapper-xml.ftl
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="${package}.mapper.${className}Mapper">
+    <#if columns??>
+    <resultMap id="BaseResultMap" type="${package}.domain.${className}">
+        <#list columns as column>
+            <#if column.columnKey = 'PRI'>
+        <id column="${column.columnName}" property="${column.changeColumnName}"/>
+            </#if>
+            <#if column.columnKey != 'PRI'>
+        <result column="${column.columnName}" property="${column.changeColumnName}"/>
+            </#if>
+        </#list>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        <#list columns as column>${column.columnName}<#if column_has_next>, </#if></#list>
+    </sql>
+    </#if>
+
+    <select id="findAll" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List"/>
+        from ${tableName}
+        <#if queryColumns??>
+        <where>
+        <#list queryColumns as column>
+            <if test="criteria.${column.changeColumnName} != null">
+            <#if column.queryType = '='>
+                and ${column.columnName} = ${symbol}{criteria.${column.changeColumnName}}
+            </#if>
+            <#if column.queryType = 'Like'>
+                and ${column.columnName} like concat('%',${symbol}{criteria.${column.changeColumnName}},'%')
+            </#if>
+            <#if column.queryType = '!='>
+                and ${column.columnName} != ${symbol}{criteria.${column.changeColumnName}}
+            </#if>
+            <#if column.queryType = 'NotNull'>
+                and ${column.columnName} is not null
+            </#if>
+            <#if column.queryType = '>='>
+                and ${column.columnName} &gt;= ${symbol}{criteria.${column.changeColumnName}}
+            </#if>
+            <#if column.queryType = '<='>
+                and ${column.columnName} &lt;= ${symbol}{criteria.${column.changeColumnName}}
+            </#if>
+            </if>
+        </#list>
+        <#if betweens??>
+            <#list betweens as column>
+            <if test="criteria.${column.changeColumnName} != null and criteria.${column.changeColumnName}.size() > 0">
+                AND ${column.columnName} BETWEEN ${symbol}{criteria.${column.changeColumnName}[0]} AND ${symbol}{criteria.${column.changeColumnName}[1]}
+            </if>
+            </#list>
+        </#if>
+        </where>
+        </#if>
+        <#if pkIdName != 'none'>
+        order by ${pkIdName} desc
+        </#if>
+    </select>
+</mapper>
\ No newline at end of file
diff --git a/oying-generator/src/main/resources/template/admin/Mapper.ftl b/oying-generator/src/main/resources/template/admin/Mapper.ftl
new file mode 100644
index 0000000..8013f84
--- /dev/null
+++ b/oying-generator/src/main/resources/template/admin/Mapper.ftl
@@ -0,0 +1,22 @@
+package ${package}.mapper;
+
+import ${package}.domain.${className};
+import ${package}.domain.dto.${className}QueryCriteria;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+/**
+* @author ${author}
+* @date ${date}
+**/
+@Mapper
+public interface ${className}Mapper extends BaseMapper<${className}> {
+
+    IPage<${className}> findAll(@Param("criteria") ${className}QueryCriteria criteria, Page<Object> page);
+
+    List<${className}> findAll(@Param("criteria") ${className}QueryCriteria criteria);
+}
diff --git a/oying-generator/src/main/resources/template/admin/QueryCriteria.ftl b/oying-generator/src/main/resources/template/admin/QueryCriteria.ftl
new file mode 100644
index 0000000..7a2a135
--- /dev/null
+++ b/oying-generator/src/main/resources/template/admin/QueryCriteria.ftl
@@ -0,0 +1,43 @@
+package ${package}.domain.dto;
+
+import lombok.Data;
+<#if queryHasTimestamp>
+import java.sql.Timestamp;
+</#if>
+<#if queryHasBigDecimal>
+import java.math.BigDecimal;
+</#if>
+<#if betweens?? && (betweens?size > 0)>
+import java.util.List;
+</#if>
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+* @author ${author}
+* @date ${date}
+**/
+@Data
+public class ${className}QueryCriteria{
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer page = 1;
+
+    @ApiModelProperty(value = "每页数据量", example = "10")
+    private Integer size = 10;
+<#if queryColumns??>
+    <#list queryColumns as column>
+
+        <#if column.remark != ''>
+    @ApiModelProperty(value = "${column.remark}")
+        <#else>
+    @ApiModelProperty(value = "${column.changeColumnName}")
+        </#if>
+    private ${column.columnType} ${column.changeColumnName};
+    </#list>
+</#if>
+<#if betweens??>
+    <#list betweens as column>
+    private List<${column.columnType}> ${column.changeColumnName};
+    </#list>
+</#if>
+}
diff --git a/oying-generator/src/main/resources/template/admin/Service.ftl b/oying-generator/src/main/resources/template/admin/Service.ftl
new file mode 100644
index 0000000..7fa07b2
--- /dev/null
+++ b/oying-generator/src/main/resources/template/admin/Service.ftl
@@ -0,0 +1,60 @@
+package ${package}.service;
+
+import ${package}.domain.${className};
+import ${package}.domain.dto.${className}QueryCriteria;
+import java.util.Map;
+import java.util.List;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.utils.PageResult;
+
+/**
+* @description 服务接口
+* @author ${author}
+* @date ${date}
+**/
+public interface ${className}Service extends IService<${className}> {
+
+    /**
+    * 查询数据分页
+    * @param criteria 条件
+    * @param page 分页参数
+    * @return PageResult
+    */
+    PageResult<${className}> queryAll(${className}QueryCriteria criteria, Page<Object> page);
+
+    /**
+    * 查询所有数据不分页
+    * @param criteria 条件参数
+    * @return List<${className}Dto>
+    */
+    List<${className}> queryAll(${className}QueryCriteria criteria);
+
+    /**
+    * 创建
+    * @param resources /
+    */
+    void create(${className} resources);
+
+    /**
+    * 编辑
+    * @param resources /
+    */
+    void update(${className} resources);
+
+    /**
+    * 多选删除
+    * @param ids /
+    */
+    void deleteAll(List<${pkColumnType}> ids);
+
+    /**
+    * 导出数据
+    * @param all 待导出的数据
+    * @param response /
+    * @throws IOException /
+    */
+    void download(List<${className}> all, HttpServletResponse response) throws IOException;
+}
diff --git a/oying-generator/src/main/resources/template/admin/ServiceImpl.ftl b/oying-generator/src/main/resources/template/admin/ServiceImpl.ftl
new file mode 100644
index 0000000..9d3de64
--- /dev/null
+++ b/oying-generator/src/main/resources/template/admin/ServiceImpl.ftl
@@ -0,0 +1,90 @@
+package ${package}.service.impl;
+
+import ${package}.domain.${className};
+<#if columns??>
+    <#list columns as column>
+        <#if column.columnKey = 'UNI'>
+            <#if column_index = 1>
+import com.oying.exception.EntityExistException;
+            </#if>
+        </#if>
+    </#list>
+</#if>
+import com.oying.utils.FileUtil;
+import lombok.RequiredArgsConstructor;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import ${package}.service.${className}Service;
+import ${package}.domain.dto.${className}QueryCriteria;
+import ${package}.mapper.${className}Mapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import com.oying.utils.PageUtil;
+import java.util.List;
+import java.util.Map;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import com.oying.utils.PageResult;
+
+/**
+* @description 服务实现
+* @author ${author}
+* @date ${date}
+**/
+@Service
+@RequiredArgsConstructor
+public class ${className}ServiceImpl extends ServiceImpl<${className}Mapper, ${className}> implements ${className}Service {
+
+    private final ${className}Mapper ${changeClassName}Mapper;
+
+    @Override
+    public PageResult<${className}> queryAll(${className}QueryCriteria criteria, Page<Object> page){
+        return PageUtil.toPage(${changeClassName}Mapper.findAll(criteria, page));
+    }
+
+    @Override
+    public List<${className}> queryAll(${className}QueryCriteria criteria){
+        return ${changeClassName}Mapper.findAll(criteria);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void create(${className} resources) {
+        ${changeClassName}Mapper.insert(resources);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void update(${className} resources) {
+        ${className} ${changeClassName} = getById(resources.get${pkCapitalColName}());
+        ${changeClassName}.copy(resources);
+        ${changeClassName}Mapper.updateById(${changeClassName});
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteAll(List<${pkColumnType}> ids) {
+        ${changeClassName}Mapper.deleteBatchIds(ids);
+    }
+
+    @Override
+    public void download(List<${className}> all, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (${className} ${changeClassName} : all) {
+            Map<String, Object> map = new LinkedHashMap<>();
+        <#list columns as column>
+            <#if column.columnKey != 'PRI'>
+            <#if column.remark != ''>
+            map.put("${column.remark}", ${changeClassName}.get${column.capitalColumnName}());
+            <#else>
+            map.put(" ${column.changeColumnName}",  ${changeClassName}.get${column.capitalColumnName}());
+            </#if>
+            </#if>
+        </#list>
+            list.add(map);
+        }
+        FileUtil.downloadExcel(list, response);
+    }
+}
diff --git a/oying-generator/src/main/resources/template/front/api.ftl b/oying-generator/src/main/resources/template/front/api.ftl
new file mode 100644
index 0000000..9587d0d
--- /dev/null
+++ b/oying-generator/src/main/resources/template/front/api.ftl
@@ -0,0 +1,27 @@
+import request from '@/utils/request'
+
+export function add(data) {
+  return request({
+    url: 'api/${changeClassName}',
+    method: 'post',
+    data
+  })
+}
+
+export function del(ids) {
+  return request({
+    url: 'api/${changeClassName}/',
+    method: 'delete',
+    data: ids
+  })
+}
+
+export function edit(data) {
+  return request({
+    url: 'api/${changeClassName}',
+    method: 'put',
+    data
+  })
+}
+
+export default { add, edit, del }
diff --git a/oying-generator/src/main/resources/template/front/index.ftl b/oying-generator/src/main/resources/template/front/index.ftl
new file mode 100644
index 0000000..4b9111a
--- /dev/null
+++ b/oying-generator/src/main/resources/template/front/index.ftl
@@ -0,0 +1,169 @@
+<#--noinspection ALL-->
+<template>
+  <div class="app-container">
+    <!--工具栏-->
+    <div class="head-container">
+    <#if hasQuery>
+      <div v-if="crud.props.searchToggle">
+        <!-- 搜索 -->
+        <#if queryColumns??>
+          <#list queryColumns as column>
+            <#if column.queryType != 'BetWeen'>
+        <label class="el-form-item-label"><#if column.remark != ''>${column.remark}<#else>${column.changeColumnName}</#if></label>
+        <el-input v-model="query.${column.changeColumnName}" clearable placeholder="<#if column.remark != ''>${column.remark}<#else>${column.changeColumnName}</#if>" style="width: 185px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
+            </#if>
+          </#list>
+        </#if>
+  <#if betweens??>
+    <#list betweens as column>
+      <#if column.queryType = 'BetWeen'>
+        <date-range-picker
+          v-model="query.${column.changeColumnName}"
+          start-placeholder="${column.changeColumnName}Start"
+          end-placeholder="${column.changeColumnName}Start"
+          class="date-item"
+        />
+      </#if>
+    </#list>
+  </#if>
+        <rrOperation :crud="crud" />
+      </div>
+    </#if>
+      <!--如果想在工具栏加入更多按钮,可以使用插槽方式, slot = 'left' or 'right'-->
+      <crudOperation :permission="permission" />
+      <!--表单组件-->
+      <el-dialog :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title" width="500px">
+        <el-form ref="form" :model="form" <#if isNotNullColumns??>:rules="rules"</#if> size="small" label-width="80px">
+    <#if columns??>
+      <#list columns as column>
+        <#if column.formShow>
+          <el-form-item label="<#if column.remark != ''>${column.remark}<#else>${column.changeColumnName}</#if>"<#if column.istNotNull> prop="${column.changeColumnName}"</#if>>
+            <#if column.formType = 'Input'>
+            <el-input v-model="form.${column.changeColumnName}" style="width: 370px;" />
+            <#elseif column.formType = 'Textarea'>
+            <el-input v-model="form.${column.changeColumnName}" :rows="3" type="textarea" style="width: 370px;" />
+            <#elseif column.formType = 'Radio'>
+              <#if (column.dictName)?? && (column.dictName)!="">
+            <el-radio v-model="form.${column.changeColumnName}" v-for="item in dict.${column.dictName}" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
+              <#else>
+                未设置字典,请手动设置 Radio
+              </#if>
+            <#elseif column.formType = 'Select'>
+              <#if (column.dictName)?? && (column.dictName)!="">
+            <el-select v-model="form.${column.changeColumnName}" filterable placeholder="请选择">
+              <el-option
+                v-for="item in dict.${column.dictName}"
+                :key="item.id"
+                :label="item.label"
+                :value="item.value" />
+            </el-select>
+              <#else>
+            未设置字典,请手动设置 Select
+              </#if>
+            <#else>
+            <el-date-picker v-model="form.${column.changeColumnName}" type="datetime" style="width: 370px;" />
+            </#if>
+          </el-form-item>
+        </#if>
+      </#list>
+    </#if>
+        </el-form>
+        <div slot="footer" class="dialog-footer">
+          <el-button type="text" @click="crud.cancelCU">取消</el-button>
+          <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
+        </div>
+      </el-dialog>
+      <!--表格渲染-->
+      <el-table ref="table" v-loading="crud.loading" :data="crud.data" size="small" style="width: 100%;" @selection-change="crud.selectionChangeHandler">
+        <el-table-column type="selection" width="55" />
+        <#if columns??>
+            <#list columns as column>
+            <#if column.columnShow>
+          <#if (column.dictName)?? && (column.dictName)!="">
+        <el-table-column prop="${column.changeColumnName}" label="<#if column.remark != ''>${column.remark}<#else>${column.changeColumnName}</#if>">
+          <template slot-scope="scope">
+            {{ dict.label.${column.dictName}[scope.row.${column.changeColumnName}] }}
+          </template>
+        </el-table-column>
+                <#else>
+        <el-table-column prop="${column.changeColumnName}" label="<#if column.remark != ''>${column.remark}<#else>${column.changeColumnName}</#if>" />
+                </#if>
+            </#if>
+            </#list>
+        </#if>
+        <el-table-column v-if="checkPer(['admin','${changeClassName}:edit','${changeClassName}:del'])" label="操作" width="150px" align="center">
+          <template slot-scope="scope">
+            <udOperation
+              :data="scope.row"
+              :permission="permission"
+            />
+          </template>
+        </el-table-column>
+      </el-table>
+      <!--分页组件-->
+      <pagination />
+    </div>
+  </div>
+</template>
+
+<script>
+import crud${className} from '@/api/${changeClassName}'
+import CRUD, { presenter, header, form, crud } from '@crud/crud'
+import rrOperation from '@crud/RR.operation'
+import crudOperation from '@crud/CRUD.operation'
+import udOperation from '@crud/UD.operation'
+import pagination from '@crud/Pagination'
+
+const defaultForm = { <#if columns??><#list columns as column>${column.changeColumnName}: null<#if column_has_next>, </#if></#list></#if> }
+export default {
+  name: '${className}',
+  components: { pagination, crudOperation, rrOperation, udOperation },
+  mixins: [presenter(), header(), form(defaultForm), crud()],
+  <#if hasDict>
+  dicts: [<#if hasDict??><#list dicts as dict>'${dict}'<#if dict_has_next>, </#if></#list></#if>],
+  </#if>
+  cruds() {
+    return CRUD({ title: '${apiAlias}', url: 'api/${changeClassName}', idField: '${pkChangeColName}', sort: '${pkChangeColName},desc', crudMethod: { ...crud${className} }})
+  },
+  data() {
+    return {
+      permission: {
+        add: ['admin', '${changeClassName}:add'],
+        edit: ['admin', '${changeClassName}:edit'],
+        del: ['admin', '${changeClassName}:del']
+      },
+      rules: {
+        <#if isNotNullColumns??>
+        <#list isNotNullColumns as column>
+        <#if column.istNotNull>
+        ${column.changeColumnName}: [
+          { required: true, message: '<#if column.remark != ''>${column.remark}</#if>不能为空', trigger: 'blur' }
+        ]<#if column_has_next>,</#if>
+        </#if>
+        </#list>
+        </#if>
+      }<#if hasQuery>,
+      queryTypeOptions: [
+        <#if queryColumns??>
+        <#list queryColumns as column>
+        <#if column.queryType != 'BetWeen'>
+        { key: '${column.changeColumnName}', display_name: '<#if column.remark != ''>${column.remark}<#else>${column.changeColumnName}</#if>' }<#if column_has_next>,</#if>
+        </#if>
+        </#list>
+        </#if>
+      ]
+      </#if>
+    }
+  },
+  methods: {
+    // 钩子:在获取表格数据之前执行,false 则代表不获取数据
+    [CRUD.HOOK.beforeRefresh]() {
+      return true
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>
diff --git a/oying-logging/pom.xml b/oying-logging/pom.xml
new file mode 100644
index 0000000..d778855
--- /dev/null
+++ b/oying-logging/pom.xml
@@ -0,0 +1,20 @@
+<?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>oying</artifactId>
+        <groupId>com.oying</groupId>
+        <version>1.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>oying-logging</artifactId>
+    <name>日志模块</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.oying</groupId>
+            <artifactId>oying-common</artifactId>
+            <version>1.1</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/oying-logging/src/main/java/com/oying/annotation/Log.java b/oying-logging/src/main/java/com/oying/annotation/Log.java
new file mode 100644
index 0000000..aad4160
--- /dev/null
+++ b/oying-logging/src/main/java/com/oying/annotation/Log.java
@@ -0,0 +1,16 @@
+package com.oying.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author Z
+ * @date 2018-11-24
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Log {
+    String value() default "";
+}
diff --git a/oying-logging/src/main/java/com/oying/aspect/LogAspect.java b/oying-logging/src/main/java/com/oying/aspect/LogAspect.java
new file mode 100644
index 0000000..772e87b
--- /dev/null
+++ b/oying-logging/src/main/java/com/oying/aspect/LogAspect.java
@@ -0,0 +1,87 @@
+package com.oying.aspect;
+
+import com.oying.service.SysLogService;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.domain.SysLog;
+import com.oying.utils.RequestHolder;
+import com.oying.utils.SecurityUtils;
+import com.oying.utils.StringUtils;
+import com.oying.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 Z
+ * @date 2018-11-24
+ */
+@Component
+@Aspect
+@Slf4j
+public class LogAspect {
+
+    private final SysLogService sysLogService;
+
+    ThreadLocal<Long> currentTime = new ThreadLocal<>();
+
+    public LogAspect(SysLogService sysLogService) {
+        this.sysLogService = sysLogService;
+    }
+
+    /**
+     * 配置切入点
+     */
+    @Pointcut("@annotation(com.oying.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();
+        SysLog sysLog = new SysLog("INFO",System.currentTimeMillis() - currentTime.get());
+        currentTime.remove();
+        HttpServletRequest request = RequestHolder.getHttpServletRequest();
+        sysLogService.save(getUsername(), StringUtils.getBrowser(request), StringUtils.getIp(request),joinPoint, sysLog);
+        return result;
+    }
+
+    /**
+     * 配置异常通知
+     *
+     * @param joinPoint join point for advice
+     * @param e exception
+     */
+    @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
+    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
+        SysLog sysLog = new SysLog("ERROR",System.currentTimeMillis() - currentTime.get());
+        currentTime.remove();
+        sysLog.setExceptionDetail(new String(ThrowableUtil.getStackTrace(e).getBytes()));
+        HttpServletRequest request = RequestHolder.getHttpServletRequest();
+        sysLogService.save(getUsername(), StringUtils.getBrowser(request), StringUtils.getIp(request), (ProceedingJoinPoint)joinPoint, sysLog);
+    }
+
+    /**
+     * 获取用户名
+     * @return /
+     */
+    public String getUsername() {
+        try {
+            return SecurityUtils.getCurrentUsername();
+        }catch (Exception e){
+            return "";
+        }
+    }
+}
diff --git a/oying-logging/src/main/java/com/oying/domain/SysLog.java b/oying-logging/src/main/java/com/oying/domain/SysLog.java
new file mode 100644
index 0000000..0d52b22
--- /dev/null
+++ b/oying-logging/src/main/java/com/oying/domain/SysLog.java
@@ -0,0 +1,66 @@
+package com.oying.domain;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import java.io.Serializable;
+import java.sql.Timestamp;
+
+/**
+ * @author Z
+ * @date 2018-11-24
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@TableName("sys_log")
+public class SysLog  implements Serializable {
+
+    @TableId(value = "log_id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "操作用户")
+    private String username;
+
+    @ApiModelProperty(value = "描述")
+    private String description;
+
+    @ApiModelProperty(value = "方法名")
+    private String method;
+
+    @ApiModelProperty(value = "参数")
+    private String params;
+
+    @ApiModelProperty(value = "日志类型")
+    private String logType;
+
+    @ApiModelProperty(value = "请求ip")
+    private String requestIp;
+
+    @ApiModelProperty(value = "地址")
+    private String address;
+
+    @ApiModelProperty(value = "浏览器")
+    private String browser;
+
+    @ApiModelProperty(value = "请求耗时")
+    private Long time;
+
+    @ApiModelProperty(value = "异常详细")
+    @JSONField(serialize = false)
+    private String exceptionDetail;
+
+    @TableField(fill = FieldFill.INSERT)
+    @ApiModelProperty(value = "创建日期:yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
+    private Timestamp createTime;
+
+    public SysLog(String logType, Long time) {
+        this.logType = logType;
+        this.time = time;
+    }
+}
diff --git a/oying-logging/src/main/java/com/oying/domain/dto/SysLogQueryCriteria.java b/oying-logging/src/main/java/com/oying/domain/dto/SysLogQueryCriteria.java
new file mode 100644
index 0000000..6f9b550
--- /dev/null
+++ b/oying-logging/src/main/java/com/oying/domain/dto/SysLogQueryCriteria.java
@@ -0,0 +1,33 @@
+package com.oying.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.sql.Timestamp;
+import java.util.List;
+
+/**
+ * 日志查询类
+ * @author Z
+ * @date 2019-6-4 09:23:07
+ */
+@Data
+public class SysLogQueryCriteria {
+
+    @ApiModelProperty(value = "模糊查询")
+    private String blurry;
+
+    @ApiModelProperty(value = "用户名称")
+    private String username;
+
+    @ApiModelProperty(value = "日志类型")
+    private String logType;
+
+    @ApiModelProperty(value = "创建时间")
+    private List<Timestamp> createTime;
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer page = 1;
+
+    @ApiModelProperty(value = "每页数据量", example = "10")
+    private Integer size = 10;
+}
diff --git a/oying-logging/src/main/java/com/oying/mapper/SysLogMapper.java b/oying-logging/src/main/java/com/oying/mapper/SysLogMapper.java
new file mode 100644
index 0000000..6d14706
--- /dev/null
+++ b/oying-logging/src/main/java/com/oying/mapper/SysLogMapper.java
@@ -0,0 +1,30 @@
+package com.oying.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.domain.SysLog;
+import com.oying.domain.dto.SysLogQueryCriteria;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author Z
+ * @description
+ * @date 2023-06-12
+ **/
+@Mapper
+public interface SysLogMapper extends BaseMapper<SysLog> {
+
+    List<SysLog> queryAll(@Param("criteria") SysLogQueryCriteria criteria);
+
+    IPage<SysLog> queryAll(@Param("criteria") SysLogQueryCriteria criteria, Page<SysLog> page);
+
+    IPage<SysLog> queryAllByUser(@Param("criteria") SysLogQueryCriteria criteria, Page<SysLog> page);
+
+    String getExceptionDetails(@Param("id") Long id);
+
+    void deleteByLevel(@Param("logType") String logType);
+}
diff --git a/oying-logging/src/main/java/com/oying/rest/SysLogController.java b/oying-logging/src/main/java/com/oying/rest/SysLogController.java
new file mode 100644
index 0000000..968c67a
--- /dev/null
+++ b/oying-logging/src/main/java/com/oying/rest/SysLogController.java
@@ -0,0 +1,100 @@
+package com.oying.rest;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.annotation.Log;
+import com.oying.domain.SysLog;
+import com.oying.domain.dto.SysLogQueryCriteria;
+import com.oying.service.SysLogService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import com.oying.utils.PageResult;
+import com.oying.utils.SecurityUtils;
+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 Z
+ * @date 2018-11-24
+ */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/logs")
+@Api(tags = "系统:日志管理")
+public class SysLogController {
+
+    private final SysLogService sysLogService;
+
+    @Log("导出数据")
+    @ApiOperation("导出数据")
+    @GetMapping(value = "/download")
+    @PreAuthorize("@el.check()")
+    public void exportLog(HttpServletResponse response, SysLogQueryCriteria criteria) throws IOException {
+        criteria.setLogType("INFO");
+        sysLogService.download(sysLogService.queryAll(criteria), response);
+    }
+
+    @Log("导出错误数据")
+    @ApiOperation("导出错误数据")
+    @GetMapping(value = "/error/download")
+    @PreAuthorize("@el.check()")
+    public void exportErrorLog(HttpServletResponse response, SysLogQueryCriteria criteria) throws IOException {
+        criteria.setLogType("ERROR");
+        sysLogService.download(sysLogService.queryAll(criteria), response);
+    }
+
+    @GetMapping
+    @ApiOperation("日志查询")
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<PageResult<SysLog>> queryLog(SysLogQueryCriteria criteria){
+        criteria.setLogType("INFO");
+        Page<SysLog> page = new Page<>(criteria.getPage(), criteria.getSize());
+        return new ResponseEntity<>(sysLogService.queryAll(criteria,page), HttpStatus.OK);
+    }
+
+    @GetMapping(value = "/user")
+    @ApiOperation("用户日志查询")
+    public ResponseEntity<PageResult<SysLog>> queryUserLog(SysLogQueryCriteria criteria){
+        criteria.setLogType("INFO");
+        criteria.setUsername(SecurityUtils.getCurrentUsername());
+        Page<SysLog> page = new Page<>(criteria.getPage(), criteria.getSize());
+        return new ResponseEntity<>(sysLogService.queryAllByUser(criteria,page), HttpStatus.OK);
+    }
+
+    @GetMapping(value = "/error")
+    @ApiOperation("错误日志查询")
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<PageResult<SysLog>> queryErrorLog(SysLogQueryCriteria criteria){
+        criteria.setLogType("ERROR");
+        Page<SysLog> page = new Page<>(criteria.getPage(), criteria.getSize());
+        return new ResponseEntity<>(sysLogService.queryAll(criteria,page), HttpStatus.OK);
+    }
+
+    @GetMapping(value = "/error/{id}")
+    @ApiOperation("日志异常详情查询")
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<Object> queryErrorLogDetail(@PathVariable Long id){
+        return new ResponseEntity<>(sysLogService.findByErrDetail(id), HttpStatus.OK);
+    }
+    @DeleteMapping(value = "/del/error")
+    @Log("删除所有ERROR日志")
+    @ApiOperation("删除所有ERROR日志")
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<Object> delAllErrorLog(){
+        sysLogService.delAllByError();
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @DeleteMapping(value = "/del/info")
+    @Log("删除所有INFO日志")
+    @ApiOperation("删除所有INFO日志")
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<Object> delAllInfoLog(){
+        sysLogService.delAllByInfo();
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}
diff --git a/oying-logging/src/main/java/com/oying/service/SysLogService.java b/oying-logging/src/main/java/com/oying/service/SysLogService.java
new file mode 100644
index 0000000..297e790
--- /dev/null
+++ b/oying-logging/src/main/java/com/oying/service/SysLogService.java
@@ -0,0 +1,77 @@
+package com.oying.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.domain.SysLog;
+import com.oying.domain.dto.SysLogQueryCriteria;
+import com.oying.utils.PageResult;
+import org.aspectj.lang.ProceedingJoinPoint;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Z
+ * @date 2018-11-24
+ */
+public interface SysLogService extends IService<SysLog>{
+
+    /**
+     * 分页查询
+     *
+     * @param criteria 查询条件
+     * @param page     分页参数
+     * @return /
+     */
+    PageResult<SysLog> queryAll(SysLogQueryCriteria criteria, Page<SysLog> page);
+
+    /**
+     * 查询全部数据
+     * @param criteria 查询条件
+     * @return /
+     */
+    List<SysLog> queryAll(SysLogQueryCriteria criteria);
+
+    /**
+     * 查询用户日志
+     * @param criteria 查询条件
+     * @param page 分页参数
+     * @return -
+     */
+    PageResult<SysLog> queryAllByUser(SysLogQueryCriteria criteria, Page<SysLog> page);
+
+    /**
+     * 保存日志数据
+     * @param username 用户
+     * @param browser 浏览器
+     * @param ip 请求IP
+     * @param joinPoint /
+     * @param sysLog 日志实体
+     */
+    void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, SysLog sysLog);
+
+    /**
+     * 查询异常详情
+     * @param id 日志ID
+     * @return Object
+     */
+    Object findByErrDetail(Long id);
+
+    /**
+     * 导出日志
+     * @param sysLogs 待导出的数据
+     * @param response /
+     * @throws IOException /
+     */
+    void download(List<SysLog> sysLogs, HttpServletResponse response) throws IOException;
+
+    /**
+     * 删除所有错误日志
+     */
+    void delAllByError();
+
+    /**
+     * 删除所有INFO日志
+     */
+    void delAllByInfo();
+}
diff --git a/oying-logging/src/main/java/com/oying/service/impl/SysLogServiceImpl.java b/oying-logging/src/main/java/com/oying/service/impl/SysLogServiceImpl.java
new file mode 100644
index 0000000..9ba9e0e
--- /dev/null
+++ b/oying-logging/src/main/java/com/oying/service/impl/SysLogServiceImpl.java
@@ -0,0 +1,169 @@
+package com.oying.service.impl;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.oying.annotation.Log;
+import com.oying.mapper.SysLogMapper;
+import com.oying.service.SysLogService;
+import lombok.RequiredArgsConstructor;
+import com.oying.utils.FileUtil;
+import com.oying.utils.PageResult;
+import com.oying.utils.PageUtil;
+import com.oying.utils.StringUtils;
+import com.oying.domain.SysLog;
+import com.oying.domain.dto.SysLogQueryCriteria;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.scheduling.annotation.Async;
+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 org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.*;
+
+/**
+ * @author Z
+ * @date 2018-11-24
+ */
+@Service
+@RequiredArgsConstructor
+public class SysLogServiceImpl extends ServiceImpl<SysLogMapper, SysLog> implements SysLogService {
+
+    private final SysLogMapper sysLogMapper;
+
+    @Override
+    public PageResult<SysLog> queryAll(SysLogQueryCriteria criteria, Page<SysLog> page) {
+        return PageUtil.toPage(sysLogMapper.queryAll(criteria, page));
+    }
+
+    @Override
+    public List<SysLog> queryAll(SysLogQueryCriteria criteria) {
+        return sysLogMapper.queryAll(criteria);
+    }
+
+    @Override
+    public PageResult<SysLog> queryAllByUser(SysLogQueryCriteria criteria, Page<SysLog> page) {
+        return PageUtil.toPage(sysLogMapper.queryAllByUser(criteria, page));
+    }
+
+    @Async
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, SysLog sysLog) {
+        if (sysLog == null) {
+            throw new IllegalArgumentException("Log 不能为 null!");
+        }
+
+        // 获取方法签名
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+        Log aopLog = method.getAnnotation(Log.class);
+
+        // 方法路径
+        String methodName = joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()";
+
+
+        // 填充基本信息
+        sysLog.setRequestIp(ip);
+        sysLog.setAddress(StringUtils.getCityInfo(sysLog.getRequestIp()));
+        sysLog.setMethod(methodName);
+        sysLog.setUsername(username);
+        sysLog.setParams(getParameter(method, joinPoint.getArgs()));
+        sysLog.setBrowser(browser);
+        sysLog.setDescription(aopLog.value());
+
+        // 保存
+        save(sysLog);
+    }
+
+    /**
+     * 根据方法和传入的参数获取请求参数
+     */
+    private String getParameter(Method method, Object[] args) {
+        List<Object> argList = new ArrayList<>();
+        Parameter[] parameters = method.getParameters();
+        for (int i = 0; i < parameters.length; i++) {
+            // 过滤掉 MultiPartFile
+            if (args[i] instanceof MultipartFile) {
+                continue;
+            }
+            // 过滤掉 HttpServletResponse
+            if (args[i] instanceof HttpServletResponse) {
+                continue;
+            }
+            // 过滤掉 HttpServletRequest
+            if (args[i] instanceof HttpServletRequest) {
+                continue;
+            }
+            //将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<>();
+                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) {
+        String details = sysLogMapper.getExceptionDetails(id);
+        return Dict.create().set("exception", details);
+    }
+
+    @Override
+    public void download(List<SysLog> sysLogs, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (SysLog sysLog : sysLogs) {
+            Map<String, Object> map = new LinkedHashMap<>();
+            map.put("用户名", sysLog.getUsername());
+            map.put("IP", sysLog.getRequestIp());
+            map.put("IP来源", sysLog.getAddress());
+            map.put("描述", sysLog.getDescription());
+            map.put("浏览器", sysLog.getBrowser());
+            map.put("请求耗时/毫秒", sysLog.getTime());
+            map.put("异常详情", sysLog.getExceptionDetail());
+            map.put("创建日期", sysLog.getCreateTime());
+            list.add(map);
+        }
+        FileUtil.downloadExcel(list, response);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delAllByError() {
+        // 删除 ERROR 级别的日志
+        sysLogMapper.deleteByLevel("ERROR");
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delAllByInfo() {
+        // 删除 INFO 级别的日志
+        sysLogMapper.deleteByLevel("INFO");
+    }
+}
diff --git a/oying-logging/src/main/resources/mapper/SysLogMapper.xml b/oying-logging/src/main/resources/mapper/SysLogMapper.xml
new file mode 100644
index 0000000..8db7580
--- /dev/null
+++ b/oying-logging/src/main/resources/mapper/SysLogMapper.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.mapper.SysLogMapper">
+
+    <sql id="info_column">
+        log_id id,description,method,params,request_ip,time,username,address,browser,exception_detail,create_time
+    </sql>
+
+    <sql id="error_column">
+        log_id id,description,method,params,request_ip,username,address,browser,exception_detail,create_time
+    </sql>
+
+    <sql id="user_column">
+        log_id id,description,request_ip,time,address,browser,create_time
+    </sql>
+
+    <sql id="query">
+        from sys_log
+        <where>
+            <if test="criteria.blurry != null and criteria.blurry != ''">
+                and (
+                username like concat('%',#{criteria.blurry},'%')
+                or description like concat('%',#{criteria.blurry},'%')
+                or address like concat('%',#{criteria.blurry},'%')
+                or request_ip like concat('%',#{criteria.blurry},'%')
+                or method like concat('%',#{criteria.blurry},'%')
+                or params like concat('%',#{criteria.blurry},'%')
+                )
+            </if>
+            <if test="criteria.username != null and criteria.username != ''">
+                and username like concat('%',#{criteria.username},'%')
+            </if>
+            <if test="criteria.logType != null and criteria.logType != ''">
+                and log_type = #{criteria.logType}
+            </if>
+            <if test="criteria.createTime != null and criteria.createTime.size() > 0">
+                and create_time between #{criteria.createTime[0]} and #{criteria.createTime[1]}
+            </if>
+        </where>
+        order by log_id desc
+    </sql>
+
+    <select id="queryAll" resultType="com.oying.domain.SysLog">
+        select
+        <choose>
+            <when test="criteria.logType == 'ERROR'">
+                <include refid="error_column"/>
+            </when>
+            <otherwise>
+                <include refid="info_column"/>
+            </otherwise>
+        </choose>
+        <include refid="query"/>
+    </select>
+
+    <select id="queryAllByUser" resultType="com.oying.domain.SysLog">
+        select
+        <include refid="user_column"/>
+        <include refid="query"/>
+    </select>
+
+    <delete id="deleteByLevel">
+        delete from sys_log where log_type = #{logType}
+    </delete>
+
+    <select id="getExceptionDetails" resultType="java.lang.String">
+        select exception_detail from sys_log where log_id = #{id}
+    </select>
+</mapper>
diff --git a/oying-system/pom.xml b/oying-system/pom.xml
new file mode 100644
index 0000000..f3e399e
--- /dev/null
+++ b/oying-system/pom.xml
@@ -0,0 +1,100 @@
+<?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>oying</artifactId>
+        <groupId>com.oying</groupId>
+        <version>1.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>oying-system</artifactId>
+    <name>核心模块</name>
+
+    <properties>
+        <jjwt.version>0.11.5</jjwt.version>
+        <!-- oshi监控需要指定jna版本, 问题详见 https://github.com/oshi/oshi/issues/1040 -->
+        <jna.version>5.8.0</jna.version>
+    </properties>
+
+    <dependencies>
+        <!-- 代码生成模块 -->
+        <dependency>
+            <groupId>com.oying</groupId>
+            <artifactId>oying-generator</artifactId>
+            <version>1.1</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.oying</groupId>
+                    <artifactId>oying-common</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- tools 模块包含了 common 和 logging 模块 -->
+        <dependency>
+            <groupId>com.oying</groupId>
+            <artifactId>oying-tools</artifactId>
+            <version>1.1</version>
+        </dependency>
+
+        <!-- quartz -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-quartz</artifactId>
+        </dependency>
+
+        <!-- jwt -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-api</artifactId>
+            <version>${jjwt.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-impl</artifactId>
+            <version>${jjwt.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-jackson</artifactId>
+            <version>${jjwt.version}</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>
+
+        <!-- 获取系统信息 -->
+        <dependency>
+            <groupId>com.github.oshi</groupId>
+            <artifactId>oshi-core</artifactId>
+            <version>6.6.5</version>
+        </dependency>
+    </dependencies>
+
+    <!-- 打包 -->
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+            <!-- 跳过单元测试 -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <skipTests>true</skipTests>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/oying-system/src/main/java/com/oying/AppRun.java b/oying-system/src/main/java/com/oying/AppRun.java
new file mode 100644
index 0000000..1616dd6
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/AppRun.java
@@ -0,0 +1,51 @@
+
+package com.oying;
+
+import io.swagger.annotations.Api;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.annotation.rest.AnonymousGetMapping;
+import com.oying.utils.SpringBeanHolder;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.ApplicationPidFileWriter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author Z
+ * @date 2018/11/15 9:20:19
+ */
+@Slf4j
+@RestController
+@Api(hidden = true)
+@SpringBootApplication
+@EnableTransactionManagement
+public class AppRun {
+
+    public static void main(String[] args) {
+        SpringApplication springApplication = new SpringApplication(AppRun.class);
+        // 监控应用的PID,启动时可指定PID路径:--spring.pid.file=/home/oying/app.pid
+        // 或者在 application.yml 添加文件路径,方便 kill,kill `cat /home/oying/app.pid`
+        springApplication.addListeners(new ApplicationPidFileWriter());
+        springApplication.run(args);
+        log.info("---------------------------------------------");
+        log.info("Local: {}", "http://localhost:8000");
+        log.info("Swagger: {}", "http://localhost:8000/doc.html");
+        log.info("---------------------------------------------");
+    }
+
+    @Bean
+    public SpringBeanHolder springContextHolder() {
+        return new SpringBeanHolder();
+    }
+
+    /**
+     * 访问首页提示
+     * @return /
+     */
+    @AnonymousGetMapping("/")
+    public String index() {
+        return "Backend service started successfully";
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/config/JobRunner.java b/oying-system/src/main/java/com/oying/modules/quartz/config/JobRunner.java
new file mode 100644
index 0000000..a6dc823
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/config/JobRunner.java
@@ -0,0 +1,36 @@
+package com.oying.modules.quartz.config;
+
+import com.oying.modules.quartz.domain.QuartzJob;
+import com.oying.modules.quartz.utils.QuartzManage;
+import lombok.RequiredArgsConstructor;
+import com.oying.modules.quartz.mapper.QuartzJobMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+import java.util.List;
+
+/**
+ * @author Z
+ * @date 2019-01-07
+ */
+@Component
+@RequiredArgsConstructor
+public class JobRunner implements ApplicationRunner {
+    private static final Logger log = LoggerFactory.getLogger(JobRunner.class);
+    private final QuartzJobMapper quartzJobMapper;
+    private final QuartzManage quartzManage;
+
+    /**
+     * 项目启动时重新激活启用的定时任务
+     *
+     * @param applicationArguments /
+     */
+    @Override
+    public void run(ApplicationArguments applicationArguments) {
+        List<QuartzJob> quartzJobs = quartzJobMapper.findByIsPauseIsFalse();
+        quartzJobs.forEach(quartzManage::addJob);
+        log.info("Timing task injection complete");
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/config/QuartzConfig.java b/oying-system/src/main/java/com/oying/modules/quartz/config/QuartzConfig.java
new file mode 100644
index 0000000..0b98934
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/config/QuartzConfig.java
@@ -0,0 +1,51 @@
+package com.oying.modules.quartz.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.quartz.spi.TriggerFiredBundle;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Scope;
+import org.springframework.lang.NonNull;
+import org.springframework.scheduling.quartz.AdaptableJobFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ * 定时任务配置
+ * @author Z
+ * @date 2019-01-07
+ */
+@Slf4j
+@Configuration
+@Scope("singleton")
+public class QuartzConfig {
+
+	/**
+	 * 解决Job中注入Spring Bean为null的问题
+	 */
+	@Component("quartzJobFactory")
+	public static class QuartzJobFactory extends AdaptableJobFactory {
+
+		private final AutowireCapableBeanFactory capableBeanFactory;
+
+		@Autowired
+		public QuartzJobFactory(AutowireCapableBeanFactory capableBeanFactory) {
+			this.capableBeanFactory = capableBeanFactory;
+		}
+
+		@NonNull
+		@Override
+		protected Object createJobInstance(@NonNull TriggerFiredBundle bundle) throws Exception {
+			try {
+				// 调用父类的方法,把Job注入到spring中
+				Object jobInstance = super.createJobInstance(bundle);
+				capableBeanFactory.autowireBean(jobInstance);
+				log.debug("Job instance created and autowired: {}", jobInstance.getClass().getName());
+				return jobInstance;
+			} catch (Exception e) {
+				log.error("Error creating job instance for bundle: {}", bundle, e);
+				throw e;
+			}
+		}
+	}
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/domain/QuartzJob.java b/oying-system/src/main/java/com/oying/modules/quartz/domain/QuartzJob.java
new file mode 100644
index 0000000..64e01ee
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/domain/QuartzJob.java
@@ -0,0 +1,70 @@
+package com.oying.modules.quartz.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import com.oying.base.BaseEntity;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * @author Z
+ * @date 2019-01-07
+ */
+@Getter
+@Setter
+@TableName("sys_quartz_job")
+public class QuartzJob extends BaseEntity implements Serializable {
+
+    public static final String JOB_KEY = "JOB_KEY";
+
+    @TableId(value = "job_id", type = IdType.AUTO)
+    @NotNull(groups = {Update.class})
+    private Long id;
+
+    @TableField(exist = false)
+    @ApiModelProperty(value = "用于子任务唯一标识", hidden = true)
+    private String uuid;
+
+    @ApiModelProperty(value = "定时器名称")
+    private String jobName;
+
+    @NotBlank
+    @ApiModelProperty(value = "Bean名称")
+    private String beanName;
+
+    @NotBlank
+    @ApiModelProperty(value = "方法名称")
+    private String methodName;
+
+    @ApiModelProperty(value = "参数")
+    private String params;
+
+    @NotBlank
+    @ApiModelProperty(value = "cron表达式")
+    private String cronExpression;
+
+    @ApiModelProperty(value = "状态,暂时或启动")
+    private Boolean isPause = false;
+
+    @ApiModelProperty(value = "负责人")
+    private String personInCharge;
+
+    @ApiModelProperty(value = "报警邮箱")
+    private String email;
+
+    @ApiModelProperty(value = "子任务")
+    private String subTask;
+
+    @ApiModelProperty(value = "失败后暂停")
+    private Boolean pauseAfterFailure;
+
+    @NotBlank
+    @ApiModelProperty(value = "备注")
+    private String description;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/domain/QuartzLog.java b/oying-system/src/main/java/com/oying/modules/quartz/domain/QuartzLog.java
new file mode 100644
index 0000000..44ee30c
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/domain/QuartzLog.java
@@ -0,0 +1,48 @@
+package com.oying.modules.quartz.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.io.Serializable;
+import java.sql.Timestamp;
+
+/**
+ * @author Z
+ * @date 2019-01-07
+ */
+@Data
+@TableName("sys_quartz_log")
+public class QuartzLog implements Serializable {
+
+    @TableId(value = "log_id", type = IdType.AUTO)
+    @ApiModelProperty(value = "ID", hidden = true)
+    private Long id;
+
+    @ApiModelProperty(value = "任务名称", hidden = true)
+    private String jobName;
+
+    @ApiModelProperty(value = "bean名称", hidden = true)
+    private String beanName;
+
+    @ApiModelProperty(value = "方法名称", hidden = true)
+    private String methodName;
+
+    @ApiModelProperty(value = "参数", hidden = true)
+    private String params;
+
+    @ApiModelProperty(value = "cron表达式", hidden = true)
+    private String cronExpression;
+
+    @ApiModelProperty(value = "状态", hidden = true)
+    private Boolean isSuccess;
+
+    @ApiModelProperty(value = "异常详情", hidden = true)
+    private String exceptionDetail;
+
+    @ApiModelProperty(value = "执行耗时", hidden = true)
+    private Long time;
+
+    @TableField(fill = FieldFill.INSERT)
+    @ApiModelProperty(value = "创建时间", hidden = true)
+    private Timestamp createTime;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/domain/dto/QuartzJobQueryCriteria.java b/oying-system/src/main/java/com/oying/modules/quartz/domain/dto/QuartzJobQueryCriteria.java
new file mode 100644
index 0000000..6807e04
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/domain/dto/QuartzJobQueryCriteria.java
@@ -0,0 +1,29 @@
+package com.oying.modules.quartz.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.sql.Timestamp;
+import java.util.List;
+
+/**
+ * @author Z
+ * @date 2019-6-4 10:33:02
+ */
+@Data
+public class QuartzJobQueryCriteria {
+
+    @ApiModelProperty(value = "定时任务名称")
+    private String jobName;
+
+    @ApiModelProperty(value = "是否成功")
+    private Boolean isSuccess;
+
+    @ApiModelProperty(value = "创建时间")
+    private List<Timestamp> createTime;
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer page = 1;
+
+    @ApiModelProperty(value = "每页数据量", example = "10")
+    private Integer size = 10;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/mapper/QuartzJobMapper.java b/oying-system/src/main/java/com/oying/modules/quartz/mapper/QuartzJobMapper.java
new file mode 100644
index 0000000..06c6ff6
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/mapper/QuartzJobMapper.java
@@ -0,0 +1,26 @@
+package com.oying.modules.quartz.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.modules.quartz.domain.QuartzJob;
+import com.oying.modules.quartz.domain.dto.QuartzJobQueryCriteria;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author Z
+ * @description
+ * @date 2023-06-12
+ **/
+@Mapper
+public interface QuartzJobMapper extends BaseMapper<QuartzJob> {
+
+    IPage<QuartzJob> findAll(@Param("criteria") QuartzJobQueryCriteria criteria, Page<Object> page);
+
+    List<QuartzJob> findAll(@Param("criteria") QuartzJobQueryCriteria criteria);
+
+    List<QuartzJob> findByIsPauseIsFalse();
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/mapper/QuartzLogMapper.java b/oying-system/src/main/java/com/oying/modules/quartz/mapper/QuartzLogMapper.java
new file mode 100644
index 0000000..6d9e53f
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/mapper/QuartzLogMapper.java
@@ -0,0 +1,23 @@
+package com.oying.modules.quartz.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.modules.quartz.domain.QuartzLog;
+import com.oying.modules.quartz.domain.dto.QuartzJobQueryCriteria;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+/**
+ * @author Z
+ * @description
+ * @date 2023-06-12
+ **/
+@Mapper
+public interface QuartzLogMapper extends BaseMapper<QuartzLog> {
+
+    IPage<QuartzLog> findAll(@Param("criteria") QuartzJobQueryCriteria criteria, Page<Object> page);
+
+    List<QuartzLog> findAll(@Param("criteria") QuartzJobQueryCriteria criteria);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/rest/QuartzJobController.java b/oying-system/src/main/java/com/oying/modules/quartz/rest/QuartzJobController.java
new file mode 100644
index 0000000..9636273
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/rest/QuartzJobController.java
@@ -0,0 +1,132 @@
+package com.oying.modules.quartz.rest;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.modules.quartz.domain.QuartzJob;
+import com.oying.modules.quartz.domain.QuartzLog;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.annotation.Log;
+import com.oying.exception.BadRequestException;
+import com.oying.modules.quartz.service.QuartzJobService;
+import com.oying.modules.quartz.domain.dto.QuartzJobQueryCriteria;
+import com.oying.utils.PageResult;
+import com.oying.utils.SpringBeanHolder;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2019-01-07
+ */
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/jobs")
+@Api(tags = "系统:定时任务管理")
+public class QuartzJobController {
+
+    private static final String ENTITY_NAME = "quartzJob";
+    private final QuartzJobService quartzJobService;
+
+    @ApiOperation("查询定时任务")
+    @GetMapping
+    @PreAuthorize("@el.check('timing:list')")
+    public ResponseEntity<PageResult<QuartzJob>> queryQuartzJob(QuartzJobQueryCriteria criteria){
+        Page<Object> page = new Page<>(criteria.getPage(), criteria.getSize());
+        return new ResponseEntity<>(quartzJobService.queryAll(criteria,page), HttpStatus.OK);
+    }
+
+    @ApiOperation("导出任务数据")
+    @GetMapping(value = "/download")
+    @PreAuthorize("@el.check('timing:list')")
+    public void exportQuartzJob(HttpServletResponse response, QuartzJobQueryCriteria criteria) throws IOException {
+        quartzJobService.download(quartzJobService.queryAll(criteria), response);
+    }
+
+    @ApiOperation("导出日志数据")
+    @GetMapping(value = "/logs/download")
+    @PreAuthorize("@el.check('timing:list')")
+    public void exportQuartzJobLog(HttpServletResponse response, QuartzJobQueryCriteria criteria) throws IOException {
+        quartzJobService.downloadLog(quartzJobService.queryAllLog(criteria), response);
+    }
+
+    @ApiOperation("查询任务执行日志")
+    @GetMapping(value = "/logs")
+    @PreAuthorize("@el.check('timing:list')")
+    public ResponseEntity<PageResult<QuartzLog>> queryQuartzJobLog(QuartzJobQueryCriteria criteria){
+        Page<Object> page = new Page<>(criteria.getPage(), criteria.getSize());
+        return new ResponseEntity<>(quartzJobService.queryAllLog(criteria,page), HttpStatus.OK);
+    }
+
+    @Log("新增定时任务")
+    @ApiOperation("新增定时任务")
+    @PostMapping
+    @PreAuthorize("@el.check('timing:add')")
+    public ResponseEntity<Object> createQuartzJob(@Validated @RequestBody QuartzJob resources){
+        if (resources.getId() != null) {
+            throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID");
+        }
+        // 验证Bean是不是合法的,合法的定时任务 Bean 需要用 @Service 定义
+        checkBean(resources.getBeanName());
+        quartzJobService.create(resources);
+        return new ResponseEntity<>(HttpStatus.CREATED);
+    }
+
+    @Log("修改定时任务")
+    @ApiOperation("修改定时任务")
+    @PutMapping
+    @PreAuthorize("@el.check('timing:edit')")
+    public ResponseEntity<Object> updateQuartzJob(@Validated(QuartzJob.Update.class) @RequestBody QuartzJob resources){
+        // 验证Bean是不是合法的,合法的定时任务 Bean 需要用 @Service 定义
+        checkBean(resources.getBeanName());
+        quartzJobService.update(resources);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Log("更改定时任务状态")
+    @ApiOperation("更改定时任务状态")
+    @PutMapping(value = "/{id}")
+    @PreAuthorize("@el.check('timing:edit')")
+    public ResponseEntity<Object> updateQuartzJobStatus(@PathVariable Long id){
+        quartzJobService.updateIsPause(quartzJobService.getById(id));
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Log("执行定时任务")
+    @ApiOperation("执行定时任务")
+    @PutMapping(value = "/exec/{id}")
+    @PreAuthorize("@el.check('timing:edit')")
+    public ResponseEntity<Object> executionQuartzJob(@PathVariable Long id){
+        quartzJobService.execution(quartzJobService.getById(id));
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Log("删除定时任务")
+    @ApiOperation("删除定时任务")
+    @DeleteMapping
+    @PreAuthorize("@el.check('timing:del')")
+    public ResponseEntity<Object> deleteQuartzJob(@RequestBody Set<Long> ids){
+        quartzJobService.delete(ids);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * 验证Bean是不是合法的,合法的定时任务 Bean 需要用 @Service 定义
+     * @param beanName Bean名称
+     */
+    private void checkBean(String beanName){
+        // 避免调用攻击者可以从SpringContextHolder获得控制jdbcTemplate类
+        // 并使用getDeclaredMethod调用jdbcTemplate的queryForMap函数,执行任意sql命令。
+        if(!SpringBeanHolder.getAllServiceBeanName().contains(beanName)){
+            throw new BadRequestException("非法的 Bean,请重新输入!");
+        }
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/service/QuartzJobService.java b/oying-system/src/main/java/com/oying/modules/quartz/service/QuartzJobService.java
new file mode 100644
index 0000000..5a6a3a4
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/service/QuartzJobService.java
@@ -0,0 +1,105 @@
+package com.oying.modules.quartz.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.modules.quartz.domain.QuartzJob;
+import com.oying.modules.quartz.domain.QuartzLog;
+import com.oying.modules.quartz.domain.dto.QuartzJobQueryCriteria;
+import com.oying.utils.PageResult;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2019-01-07
+ */
+public interface QuartzJobService extends IService<QuartzJob> {
+
+    /**
+     * 分页查询
+     *
+     * @param criteria 条件
+     * @param page     分页参数
+     * @return /
+     */
+    PageResult<QuartzJob> queryAll(QuartzJobQueryCriteria criteria, Page<Object> page);
+
+    /**
+     * 查询全部
+     * @param criteria 条件
+     * @return /
+     */
+    List<QuartzJob> queryAll(QuartzJobQueryCriteria criteria);
+
+    /**
+     * 分页查询日志
+     *
+     * @param criteria 条件
+     * @param page     分页参数
+     * @return /
+     */
+    PageResult<QuartzLog> queryAllLog(QuartzJobQueryCriteria criteria, Page<Object> page);
+
+    /**
+     * 查询全部
+     * @param criteria 条件
+     * @return /
+     */
+    List<QuartzLog> queryAllLog(QuartzJobQueryCriteria criteria);
+
+    /**
+     * 创建
+     * @param resources /
+     */
+    void create(QuartzJob resources);
+
+    /**
+     * 编辑
+     * @param resources /
+     */
+    void update(QuartzJob resources);
+
+    /**
+     * 删除任务
+     * @param ids /
+     */
+    void delete(Set<Long> ids);
+
+    /**
+     * 更改定时任务状态
+     * @param quartzJob /
+     */
+    void updateIsPause(QuartzJob quartzJob);
+
+    /**
+     * 立即执行定时任务
+     * @param quartzJob /
+     */
+    void execution(QuartzJob quartzJob);
+
+    /**
+     * 导出定时任务
+     * @param queryAll 待导出的数据
+     * @param response /
+     * @throws IOException /
+     */
+    void download(List<QuartzJob> queryAll, HttpServletResponse response) throws IOException;
+
+    /**
+     * 导出定时任务日志
+     * @param queryAllLog 待导出的数据
+     * @param response /
+     * @throws IOException /
+     */
+    void downloadLog(List<QuartzLog> queryAllLog, HttpServletResponse response) throws IOException;
+
+    /**
+     * 执行子任务
+     * @param tasks /
+     * @throws InterruptedException /
+     */
+    void executionSubJob(String[] tasks) throws InterruptedException;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/service/impl/QuartzJobServiceImpl.java b/oying-system/src/main/java/com/oying/modules/quartz/service/impl/QuartzJobServiceImpl.java
new file mode 100644
index 0000000..8e84e9c
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/service/impl/QuartzJobServiceImpl.java
@@ -0,0 +1,176 @@
+package com.oying.modules.quartz.service.impl;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.oying.modules.quartz.domain.QuartzJob;
+import com.oying.modules.quartz.domain.QuartzLog;
+import com.oying.modules.quartz.utils.QuartzManage;
+import com.oying.utils.*;
+import lombok.RequiredArgsConstructor;
+import com.oying.exception.BadRequestException;
+import com.oying.modules.quartz.mapper.QuartzJobMapper;
+import com.oying.modules.quartz.mapper.QuartzLogMapper;
+import com.oying.modules.quartz.service.QuartzJobService;
+import com.oying.modules.quartz.domain.dto.QuartzJobQueryCriteria;
+import org.quartz.CronExpression;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author Z
+ * @date 2019-01-07
+ */
+@RequiredArgsConstructor
+@Service(value = "quartzJobService")
+public class QuartzJobServiceImpl extends ServiceImpl<QuartzJobMapper, QuartzJob> implements QuartzJobService {
+
+    private final QuartzJobMapper quartzJobMapper;
+    private final QuartzLogMapper quartzLogMapper;
+    private final QuartzManage quartzManage;
+    private final RedisUtils redisUtils;
+
+    @Override
+    public PageResult<QuartzJob> queryAll(QuartzJobQueryCriteria criteria, Page<Object> page){
+        return PageUtil.toPage(quartzJobMapper.findAll(criteria, page));
+    }
+
+    @Override
+    public PageResult<QuartzLog> queryAllLog(QuartzJobQueryCriteria criteria, Page<Object> page){
+        return PageUtil.toPage(quartzLogMapper.findAll(criteria, page));
+    }
+
+    @Override
+    public List<QuartzJob> queryAll(QuartzJobQueryCriteria criteria) {
+        return quartzJobMapper.findAll(criteria);
+    }
+
+    @Override
+    public List<QuartzLog> queryAllLog(QuartzJobQueryCriteria criteria) {
+        return quartzLogMapper.findAll(criteria);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void create(QuartzJob resources) {
+        if (!CronExpression.isValidExpression(resources.getCronExpression())){
+            throw new BadRequestException("cron表达式格式错误");
+        }
+        save(resources);
+        quartzManage.addJob(resources);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void update(QuartzJob resources) {
+        if (!CronExpression.isValidExpression(resources.getCronExpression())){
+            throw new BadRequestException("cron表达式格式错误");
+        }
+        if(StringUtils.isNotBlank(resources.getSubTask())){
+            List<String> tasks = Arrays.asList(resources.getSubTask().split("[,,]"));
+            if (tasks.contains(resources.getId().toString())) {
+                throw new BadRequestException("子任务中不能添加当前任务ID");
+            }
+        }
+        saveOrUpdate(resources);
+        quartzManage.updateJobCron(resources);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateIsPause(QuartzJob quartzJob) {
+        // 置换暂停状态
+        if (quartzJob.getIsPause()) {
+            quartzManage.resumeJob(quartzJob);
+            quartzJob.setIsPause(false);
+        } else {
+            quartzManage.pauseJob(quartzJob);
+            quartzJob.setIsPause(true);
+        }
+        saveOrUpdate(quartzJob);
+    }
+
+    @Override
+    public void execution(QuartzJob quartzJob) {
+        quartzManage.runJobNow(quartzJob);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delete(Set<Long> ids) {
+        for (Long id : ids) {
+            QuartzJob quartzJob = getById(id);
+            quartzManage.deleteJob(quartzJob);
+            removeById(quartzJob);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void executionSubJob(String[] tasks) throws InterruptedException {
+        for (String id : tasks) {
+            if (StrUtil.isBlank(id)) {
+                // 如果是手动清除子任务id,会出现id为空字符串的问题
+                continue;
+            }
+            QuartzJob quartzJob = getById(Long.parseLong(id));
+            // 执行任务
+            String uuid = IdUtil.simpleUUID();
+            quartzJob.setUuid(uuid);
+            // 执行任务
+            execution(quartzJob);
+            // 获取执行状态,如果执行失败则停止后面的子任务执行
+            Boolean result = redisUtils.get(uuid, Boolean.class);
+            while (result == null) {
+                // 休眠5秒,再次获取子任务执行情况
+                Thread.sleep(5000);
+                result = redisUtils.get(uuid, Boolean.class);
+            }
+            if(!result){
+                redisUtils.del(uuid);
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void download(List<QuartzJob> quartzJobs, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (QuartzJob quartzJob : quartzJobs) {
+            Map<String,Object> map = new LinkedHashMap<>();
+            map.put("任务名称", quartzJob.getJobName());
+            map.put("Bean名称", quartzJob.getBeanName());
+            map.put("执行方法", quartzJob.getMethodName());
+            map.put("参数", quartzJob.getParams());
+            map.put("表达式", quartzJob.getCronExpression());
+            map.put("状态", quartzJob.getIsPause() ? "暂停中" : "运行中");
+            map.put("描述", quartzJob.getDescription());
+            map.put("创建日期", quartzJob.getCreateTime());
+            list.add(map);
+        }
+        FileUtil.downloadExcel(list, response);
+    }
+
+    @Override
+    public void downloadLog(List<QuartzLog> queryAllLog, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (QuartzLog quartzLog : queryAllLog) {
+            Map<String,Object> map = new LinkedHashMap<>();
+            map.put("任务名称", quartzLog.getJobName());
+            map.put("Bean名称", quartzLog.getBeanName());
+            map.put("执行方法", quartzLog.getMethodName());
+            map.put("参数", quartzLog.getParams());
+            map.put("表达式", quartzLog.getCronExpression());
+            map.put("异常详情", quartzLog.getExceptionDetail());
+            map.put("耗时/毫秒", quartzLog.getTime());
+            map.put("状态", quartzLog.getIsSuccess() ? "成功" : "失败");
+            map.put("创建日期", quartzLog.getCreateTime());
+            list.add(map);
+        }
+        FileUtil.downloadExcel(list, response);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/task/TestTask.java b/oying-system/src/main/java/com/oying/modules/quartz/task/TestTask.java
new file mode 100644
index 0000000..2588016
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/task/TestTask.java
@@ -0,0 +1,26 @@
+package com.oying.modules.quartz.task;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * 测试用
+ * @author Z
+ * @date 2019-01-08
+ */
+@Slf4j
+@Service
+public class TestTask {
+
+    public void run(){
+        log.info("run 执行成功");
+    }
+
+    public void run1(String str){
+        log.info("run1 执行成功,参数为: {}", str);
+    }
+
+    public void run2(){
+        log.info("run2 执行成功");
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/utils/ExecutionJob.java b/oying-system/src/main/java/com/oying/modules/quartz/utils/ExecutionJob.java
new file mode 100644
index 0000000..c604357
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/utils/ExecutionJob.java
@@ -0,0 +1,116 @@
+package com.oying.modules.quartz.utils;
+
+import cn.hutool.extra.template.Template;
+import cn.hutool.extra.template.TemplateConfig;
+import cn.hutool.extra.template.TemplateEngine;
+import cn.hutool.extra.template.TemplateUtil;
+import com.oying.modules.quartz.domain.QuartzJob;
+import com.oying.modules.quartz.domain.QuartzLog;
+import com.oying.domain.dto.EmailDto;
+import com.oying.modules.quartz.mapper.QuartzLogMapper;
+import com.oying.modules.quartz.service.QuartzJobService;
+import com.oying.service.EmailService;
+import com.oying.utils.RedisUtils;
+import com.oying.utils.SpringBeanHolder;
+import com.oying.utils.StringUtils;
+import com.oying.utils.ThrowableUtil;
+import org.quartz.JobExecutionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+import java.util.*;
+import java.util.concurrent.*;
+
+/**
+ * 参考人人开源,<a href="https://gitee.com/renrenio/renren-security">...</a>
+ * @author Z
+ * @date 2019-01-07
+ */
+public class ExecutionJob extends QuartzJobBean {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    // 此处仅供参考,可根据任务执行情况自定义线程池参数
+    private final ThreadPoolTaskExecutor executor = SpringBeanHolder.getBean("taskAsync");
+
+    @Override
+    public void executeInternal(JobExecutionContext context) {
+        // 获取任务
+        QuartzJob quartzJob = (QuartzJob) context.getMergedJobDataMap().get(QuartzJob.JOB_KEY);
+        // 获取spring bean
+        QuartzLogMapper quartzLogMapper = SpringBeanHolder.getBean(QuartzLogMapper.class);
+        QuartzJobService quartzJobService = SpringBeanHolder.getBean(QuartzJobService.class);
+        RedisUtils redisUtils = SpringBeanHolder.getBean(RedisUtils.class);
+
+        String uuid = quartzJob.getUuid();
+
+        QuartzLog log = new QuartzLog();
+        log.setJobName(quartzJob.getJobName());
+        log.setBeanName(quartzJob.getBeanName());
+        log.setMethodName(quartzJob.getMethodName());
+        log.setParams(quartzJob.getParams());
+        long startTime = System.currentTimeMillis();
+        log.setCronExpression(quartzJob.getCronExpression());
+        try {
+            // 执行任务
+            QuartzRunnable task = new QuartzRunnable(quartzJob.getBeanName(), quartzJob.getMethodName(), quartzJob.getParams());
+            Future<?> future = executor.submit(task);
+            future.get();
+            long times = System.currentTimeMillis() - startTime;
+            log.setTime(times);
+            if(StringUtils.isNotBlank(uuid)) {
+                redisUtils.set(uuid, true);
+            }
+            // 任务状态
+            log.setIsSuccess(true);
+            logger.info("任务执行成功,任务名称:{}, 执行时间:{}毫秒", quartzJob.getJobName(), times);
+            // 判断是否存在子任务
+            if(StringUtils.isNotBlank(quartzJob.getSubTask())){
+                String[] tasks = quartzJob.getSubTask().split("[,,]");
+                // 执行子任务
+                quartzJobService.executionSubJob(tasks);
+            }
+        } catch (Exception e) {
+            if(StringUtils.isNotBlank(uuid)) {
+                redisUtils.set(uuid, false);
+            }
+            logger.error("任务执行失败,任务名称:{}", quartzJob.getJobName());
+            long times = System.currentTimeMillis() - startTime;
+            log.setTime(times);
+            // 任务状态 0:成功 1:失败
+            log.setIsSuccess(false);
+            log.setExceptionDetail(ThrowableUtil.getStackTrace(e));
+            // 任务如果失败了则暂停
+            if(quartzJob.getPauseAfterFailure() != null && quartzJob.getPauseAfterFailure()){
+                quartzJob.setIsPause(false);
+                //更新状态
+                quartzJobService.updateIsPause(quartzJob);
+            }
+            if(quartzJob.getEmail() != null){
+                EmailService emailService = SpringBeanHolder.getBean(EmailService.class);
+                // 邮箱报警
+                if(StringUtils.isNoneBlank(quartzJob.getEmail())){
+                    EmailDto emailDto = taskAlarm(quartzJob, ThrowableUtil.getStackTrace(e));
+                    emailService.send(emailDto, emailService.find());
+                }
+            }
+        } finally {
+            quartzLogMapper.insert(log);
+        }
+    }
+
+    private EmailDto taskAlarm(QuartzJob quartzJob, String msg) {
+        EmailDto emailDto = new EmailDto();
+        emailDto.setSubject("定时任务【"+ quartzJob.getJobName() +"】执行失败,请尽快处理!");
+        Map<String, Object> data = new HashMap<>(16);
+        data.put("task", quartzJob);
+        data.put("msg", msg);
+        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
+        Template template = engine.getTemplate("taskAlarm.ftl");
+        emailDto.setContent(template.render(data));
+        List<String> emails = Arrays.asList(quartzJob.getEmail().split("[,,]"));
+        emailDto.setTos(emails);
+        return emailDto;
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/utils/QuartzManage.java b/oying-system/src/main/java/com/oying/modules/quartz/utils/QuartzManage.java
new file mode 100644
index 0000000..ea734e5
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/utils/QuartzManage.java
@@ -0,0 +1,162 @@
+package com.oying.modules.quartz.utils;
+
+import com.oying.modules.quartz.domain.QuartzJob;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.exception.BadRequestException;
+import org.quartz.*;
+import org.quartz.impl.triggers.CronTriggerImpl;
+import org.springframework.stereotype.Component;
+import javax.annotation.Resource;
+import java.util.Date;
+import static org.quartz.TriggerBuilder.newTrigger;
+
+/**
+ * @author Z
+ * @date 2019-01-07
+ */
+@Slf4j
+@Component
+public class QuartzManage {
+
+    private static final String JOB_NAME = "TASK_";
+
+    @Resource
+    private Scheduler scheduler;
+
+    public void addJob(QuartzJob quartzJob){
+        try {
+            // 构建job信息
+            JobDetail jobDetail = JobBuilder.newJob(ExecutionJob.class).
+                    withIdentity(JOB_NAME + quartzJob.getId()).build();
+
+            //通过触发器名和cron 表达式创建 Trigger
+            Trigger cronTrigger = newTrigger()
+                    .withIdentity(JOB_NAME + quartzJob.getId())
+                    .startNow()
+                    .withSchedule(CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression()))
+                    .build();
+
+            cronTrigger.getJobDataMap().put(QuartzJob.JOB_KEY, quartzJob);
+
+            //重置启动时间
+            ((CronTriggerImpl)cronTrigger).setStartTime(new Date());
+
+            //执行定时任务,如果是持久化的,这里会报错,捕获输出
+            try {
+                scheduler.scheduleJob(jobDetail,cronTrigger);
+            } catch (ObjectAlreadyExistsException e) {
+                log.warn("定时任务已存在,跳过加载");
+            }
+
+            // 暂停任务
+            if (quartzJob.getIsPause()) {
+                pauseJob(quartzJob);
+            }
+        } catch (Exception e){
+            log.error("创建定时任务失败", e);
+            throw new BadRequestException("创建定时任务失败");
+        }
+    }
+
+    /**
+     * 更新job cron表达式
+     * @param quartzJob /
+     */
+    public void updateJobCron(QuartzJob quartzJob){
+        try {
+            TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
+            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+            // 如果不存在则创建一个定时任务
+            if(trigger == null){
+                addJob(quartzJob);
+                trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+            }
+            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression());
+            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
+            //重置启动时间
+            ((CronTriggerImpl)trigger).setStartTime(new Date());
+            trigger.getJobDataMap().put(QuartzJob.JOB_KEY,quartzJob);
+
+            scheduler.rescheduleJob(triggerKey, trigger);
+            // 暂停任务
+            if (quartzJob.getIsPause()) {
+                pauseJob(quartzJob);
+            }
+        } catch (Exception e){
+            log.error("更新定时任务失败", e);
+            throw new BadRequestException("更新定时任务失败");
+        }
+
+    }
+
+    /**
+     * 删除一个job
+     * @param quartzJob /
+     */
+    public void deleteJob(QuartzJob quartzJob){
+        try {
+            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
+            scheduler.pauseJob(jobKey);
+            scheduler.deleteJob(jobKey);
+        } catch (Exception e){
+            log.error("删除定时任务失败", e);
+            throw new BadRequestException("删除定时任务失败");
+        }
+    }
+
+    /**
+     * 恢复一个job
+     * @param quartzJob /
+     */
+    public void resumeJob(QuartzJob quartzJob){
+        try {
+            TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
+            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+            // 如果不存在则创建一个定时任务
+            if(trigger == null) {
+                addJob(quartzJob);
+            }
+            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
+            scheduler.resumeJob(jobKey);
+        } catch (Exception e){
+            log.error("恢复定时任务失败", e);
+            throw new BadRequestException("恢复定时任务失败");
+        }
+    }
+
+    /**
+     * 立即执行job
+     * @param quartzJob /
+     */
+    public void runJobNow(QuartzJob quartzJob){
+        try {
+            TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
+            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+            // 如果不存在则创建一个定时任务
+            if(trigger == null) {
+                addJob(quartzJob);
+            }
+            JobDataMap dataMap = new JobDataMap();
+            dataMap.put(QuartzJob.JOB_KEY, quartzJob);
+            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
+            scheduler.triggerJob(jobKey,dataMap);
+        } catch (Exception e){
+            log.error("定时任务执行失败", e);
+            throw new BadRequestException("定时任务执行失败");
+        }
+    }
+
+    /**
+     * 暂停一个job
+     * @param quartzJob /
+     */
+    public void pauseJob(QuartzJob quartzJob){
+        try {
+            JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
+            scheduler.pauseJob(jobKey);
+        } catch (Exception e){
+            log.error("定时任务暂停失败", e);
+            throw new BadRequestException("定时任务暂停失败");
+        }
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/quartz/utils/QuartzRunnable.java b/oying-system/src/main/java/com/oying/modules/quartz/utils/QuartzRunnable.java
new file mode 100644
index 0000000..f2f5577
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/quartz/utils/QuartzRunnable.java
@@ -0,0 +1,43 @@
+package com.oying.modules.quartz.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import com.oying.utils.SpringBeanHolder;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.ReflectionUtils;
+import java.lang.reflect.Method;
+import java.util.concurrent.Callable;
+
+/**
+ * 执行定时任务
+ * @author Z
+ */
+@Slf4j
+public class QuartzRunnable implements Callable<Object> {
+
+	private final Object target;
+	private final Method method;
+	private final String params;
+
+	QuartzRunnable(String beanName, String methodName, String params)
+			throws NoSuchMethodException, SecurityException {
+		this.target = SpringBeanHolder.getBean(beanName);
+		this.params = params;
+		if (StringUtils.isNotBlank(params)) {
+			this.method = target.getClass().getDeclaredMethod(methodName, String.class);
+		} else {
+			this.method = target.getClass().getDeclaredMethod(methodName);
+		}
+	}
+
+	@Override
+	@SuppressWarnings({"unchecked","all"})
+	public Object call() throws Exception {
+		ReflectionUtils.makeAccessible(method);
+		if (StringUtils.isNotBlank(params)) {
+			method.invoke(target, params);
+		} else {
+			method.invoke(target);
+		}
+		return null;
+	}
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/config/CaptchaConfig.java b/oying-system/src/main/java/com/oying/modules/security/config/CaptchaConfig.java
new file mode 100644
index 0000000..aec11d7
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/config/CaptchaConfig.java
@@ -0,0 +1,120 @@
+package com.oying.modules.security.config;
+
+import com.oying.modules.security.config.enums.LoginCodeEnum;
+import com.wf.captcha.*;
+import com.wf.captcha.base.Captcha;
+import lombok.Data;
+import lombok.Getter;
+import com.oying.exception.BadRequestException;
+import com.oying.utils.StringUtils;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import java.awt.*;
+
+/**
+ * 登录验证码配置信息
+ * @author Z
+ * @date 2025-01-13
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "login.code")
+public class CaptchaConfig {
+
+    /**
+     * 验证码配置
+     */
+    @Getter
+    private LoginCodeEnum codeType;
+
+    /**
+     * 验证码有效期 分钟
+     */
+    private Long expiration = 5L;
+
+    /**
+     * 验证码内容长度
+     */
+    private int length = 4;
+
+    /**
+     * 验证码宽度
+     */
+    private int width = 111;
+
+    /**
+     * 验证码高度
+     */
+    private int height = 36;
+
+    /**
+     * 验证码字体
+     */
+    private String fontName;
+
+    /**
+     * 字体大小
+     */
+    private int fontSize = 25;
+
+    /**
+     * 依据配置信息生产验证码
+     * @return /
+     */
+    public Captcha getCaptcha() {
+        Captcha captcha;
+        switch (codeType) {
+            case ARITHMETIC:
+                // 算术类型 https://gitee.com/whvse/EasyCaptcha
+                captcha = new FixedArithmeticCaptcha(width, height);
+                // 几位数运算,默认是两位
+                captcha.setLen(length);
+                break;
+            case CHINESE:
+                captcha = new ChineseCaptcha(width, height);
+                captcha.setLen(length);
+                break;
+            case CHINESE_GIF:
+                captcha = new ChineseGifCaptcha(width, height);
+                captcha.setLen(length);
+                break;
+            case GIF:
+                captcha = new GifCaptcha(width, height);
+                captcha.setLen(length);
+                break;
+            case SPEC:
+                captcha = new SpecCaptcha(width, height);
+                captcha.setLen(length);
+                break;
+            default:
+                throw new BadRequestException("验证码配置信息错误!正确配置查看 LoginCodeEnum ");
+        }
+        if(StringUtils.isNotBlank(fontName)){
+            captcha.setFont(new Font(fontName, Font.PLAIN, fontSize));
+        }
+        return captcha;
+    }
+
+    static class FixedArithmeticCaptcha extends ArithmeticCaptcha {
+        public FixedArithmeticCaptcha(int width, int height) {
+            super(width, height);
+        }
+
+        @Override
+        protected char[] alphas() {
+            // 生成随机数字和运算符
+            int n1 = num(1, 10), n2 = num(1, 10);
+            int opt = num(3);
+
+            // 计算结果
+            int res = new int[]{n1 + n2, n1 - n2, n1 * n2}[opt];
+            // 转换为字符运算符
+            char optChar = "+-x".charAt(opt);
+
+            this.setArithmeticString(String.format("%s%c%s=?", n1, optChar, n2));
+            this.chars = String.valueOf(res);
+
+            return chars.toCharArray();
+        }
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/config/LoginProperties.java b/oying-system/src/main/java/com/oying/modules/security/config/LoginProperties.java
new file mode 100644
index 0000000..cb4a5a2
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/config/LoginProperties.java
@@ -0,0 +1,24 @@
+package com.oying.modules.security.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 配置文件读取
+ *
+ * @author Z
+ * @date loginCode.length0loginCode.length0/6/10 17:loginCode.length6
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "login")
+public class LoginProperties {
+
+    /**
+     * 账号单用户 登录
+     */
+    private boolean singleLogin = false;
+
+    public static final String cacheKey = "user_login_cache:";
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/config/SecurityProperties.java b/oying-system/src/main/java/com/oying/modules/security/config/SecurityProperties.java
new file mode 100644
index 0000000..ff07ca3
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/config/SecurityProperties.java
@@ -0,0 +1,61 @@
+package com.oying.modules.security.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Jwt参数配置
+ *
+ * @author Z
+ * @date 2019年11月28日
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "jwt")
+public class SecurityProperties {
+
+    /**
+     * Request Headers : Authorization
+     */
+    private String header;
+
+    /**
+     * 令牌前缀,最后留个空格 Bearer
+     */
+    private String tokenStartWith;
+
+    /**
+     * 必须使用最少88位的Base64对该令牌进行编码
+     */
+    private String base64Secret;
+
+    /**
+     * 令牌过期时间 此处单位/毫秒
+     */
+    private Long tokenValidityInSeconds;
+
+    /**
+     * 在线用户 key,根据 key 查询 redis 中在线用户的数据
+     */
+    private String onlineKey;
+
+    /**
+     * 验证码 key
+     */
+    private String codeKey;
+
+    /**
+     * token 续期检查
+     */
+    private Long detect;
+
+    /**
+     * 续期时间
+     */
+    private Long renew;
+
+    public String getTokenStartWith() {
+        return tokenStartWith + " ";
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/config/SpringSecurityConfig.java b/oying-system/src/main/java/com/oying/modules/security/config/SpringSecurityConfig.java
new file mode 100644
index 0000000..f34900a
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/config/SpringSecurityConfig.java
@@ -0,0 +1,119 @@
+package com.oying.modules.security.config;
+
+import com.oying.modules.security.security.JwtAccessDeniedHandler;
+import com.oying.modules.security.security.JwtAuthenticationEntryPoint;
+import com.oying.modules.security.security.TokenConfigurer;
+import com.oying.modules.security.security.TokenProvider;
+import com.oying.modules.security.service.OnlineUserService;
+import lombok.RequiredArgsConstructor;
+import com.oying.utils.AnonTagUtils;
+import com.oying.utils.enums.RequestMethodEnum;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.core.GrantedAuthorityDefaults;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.web.filter.CorsFilter;
+import java.util.*;
+
+/**
+ * @author Z
+ */
+@Configuration
+@RequiredArgsConstructor
+@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+public class SpringSecurityConfig {
+
+    private final TokenProvider tokenProvider;
+    private final CorsFilter corsFilter;
+    private final JwtAuthenticationEntryPoint authenticationErrorHandler;
+    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
+    private final ApplicationContext applicationContext;
+    private final SecurityProperties properties;
+    private final OnlineUserService onlineUserService;
+
+    @Bean
+    GrantedAuthorityDefaults grantedAuthorityDefaults() {
+        // 去除 ROLE_ 前缀
+        return new GrantedAuthorityDefaults("");
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        // 密码加密方式
+        return new BCryptPasswordEncoder();
+    }
+
+    @Bean
+    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
+        // 获取匿名标记
+        Map<String, Set<String>> anonymousUrls = AnonTagUtils.getAnonymousUrl(applicationContext);
+        return httpSecurity
+                // 禁用 CSRF
+                .csrf().disable()
+                .addFilter(corsFilter)
+                // 授权异常
+                .exceptionHandling()
+                .authenticationEntryPoint(authenticationErrorHandler)
+                .accessDeniedHandler(jwtAccessDeniedHandler)
+                // 防止iframe 造成跨域
+                .and()
+                .headers()
+                .frameOptions()
+                .disable()
+                // 不创建会话
+                .and()
+                .sessionManagement()
+                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+                .and()
+                .authorizeRequests()
+                // 静态资源等等
+                .antMatchers(
+                        HttpMethod.GET,
+                        "/*.html",
+                        "/**/*.html",
+                        "/**/*.css",
+                        "/**/*.js",
+                        "/webSocket/**"
+                ).permitAll()
+                // swagger 文档
+                .antMatchers("/swagger-ui.html").permitAll()
+                .antMatchers("/swagger-resources/**").permitAll()
+                .antMatchers("/webjars/**").permitAll()
+                .antMatchers("/*/api-docs").permitAll()
+                // 文件
+                .antMatchers("/avatar/**").permitAll()
+                .antMatchers("/file/**").permitAll()
+                // 阿里巴巴 druid
+                .antMatchers("/druid/**").permitAll()
+                // 放行OPTIONS请求
+                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
+                // 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型
+                // GET
+                .antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()
+                // POST
+                .antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()
+                // PUT
+                .antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()
+                // PATCH
+                .antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()
+                // DELETE
+                .antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()
+                // 所有类型的接口都放行
+                .antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()
+                // 所有请求都需要认证
+                .anyRequest().authenticated()
+                .and().apply(securityConfigurerAdapter())
+                .and().build();
+    }
+
+    private TokenConfigurer securityConfigurerAdapter() {
+        return new TokenConfigurer(tokenProvider, properties, onlineUserService);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/config/enums/LoginCodeEnum.java b/oying-system/src/main/java/com/oying/modules/security/config/enums/LoginCodeEnum.java
new file mode 100644
index 0000000..6e12696
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/config/enums/LoginCodeEnum.java
@@ -0,0 +1,31 @@
+package com.oying.modules.security.config.enums;
+
+/**
+ * 验证码配置枚举
+ *
+ * @author Z
+ * @date 2020/6/10 17:40
+ */
+
+public enum LoginCodeEnum {
+    /**
+     * 算数
+     */
+    ARITHMETIC,
+    /**
+     * 中文
+     */
+    CHINESE,
+    /**
+     * 中文闪图
+     */
+    CHINESE_GIF,
+    /**
+     * 闪图
+     */
+    GIF,
+    /**
+     * 静态
+     */
+    SPEC
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/rest/AuthController.java b/oying-system/src/main/java/com/oying/modules/security/rest/AuthController.java
new file mode 100644
index 0000000..34ad8ad
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/rest/AuthController.java
@@ -0,0 +1,139 @@
+package com.oying.modules.security.rest;
+
+import cn.hutool.core.util.IdUtil;
+import com.oying.modules.security.config.CaptchaConfig;
+import com.oying.modules.security.config.LoginProperties;
+import com.oying.modules.security.config.SecurityProperties;
+import com.oying.modules.security.config.enums.LoginCodeEnum;
+import com.oying.modules.security.security.TokenProvider;
+import com.oying.modules.security.service.OnlineUserService;
+import com.oying.modules.security.service.UserDetailsServiceImpl;
+import com.oying.modules.security.service.dto.AuthUserDto;
+import com.oying.modules.security.service.dto.JwtUserDto;
+import com.wf.captcha.base.Captcha;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.annotation.Log;
+import com.oying.annotation.rest.AnonymousDeleteMapping;
+import com.oying.annotation.rest.AnonymousGetMapping;
+import com.oying.annotation.rest.AnonymousPostMapping;
+import com.oying.config.properties.RsaProperties;
+import com.oying.exception.BadRequestException;
+import com.oying.utils.RsaUtils;
+import com.oying.utils.RedisUtils;
+import com.oying.utils.SecurityUtils;
+import com.oying.utils.StringUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Z
+ * @date 2018-11-23
+ * 授权、根据token获取用户详细信息
+ */
+@Slf4j
+@RestController
+@RequestMapping("/auth")
+@RequiredArgsConstructor
+@Api(tags = "系统:系统授权接口")
+public class AuthController {
+
+    private final SecurityProperties properties;
+    private final RedisUtils redisUtils;
+    private final OnlineUserService onlineUserService;
+    private final TokenProvider tokenProvider;
+    private final CaptchaConfig captchaConfig;
+    private final LoginProperties loginProperties;
+    private final PasswordEncoder passwordEncoder;
+    private final UserDetailsServiceImpl userDetailsService;
+
+    @Log("用户登录")
+    @ApiOperation("登录授权")
+    @AnonymousPostMapping(value = "/login")
+    public ResponseEntity<Object> login(@Validated @RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception {
+        // 密码解密
+        String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
+        // 查询验证码
+        String code = redisUtils.get(authUser.getUuid(), String.class);
+        // 清除验证码
+        redisUtils.del(authUser.getUuid());
+        if (StringUtils.isBlank(code)) {
+            throw new BadRequestException("验证码不存在或已过期");
+        }
+        if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {
+            throw new BadRequestException("验证码错误");
+        }
+        // 获取用户信息
+        JwtUserDto jwtUser = userDetailsService.loadUserByUsername(authUser.getUsername());
+        // 验证用户密码
+        if (!passwordEncoder.matches(password, jwtUser.getPassword())) {
+            throw new BadRequestException("登录密码错误");
+        }
+        Authentication authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities());
+        SecurityContextHolder.getContext().setAuthentication(authentication);
+        // 生成令牌
+        String token = tokenProvider.createToken(jwtUser);
+        // 返回 token 与 用户信息
+        Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
+            put("token", properties.getTokenStartWith() + token);
+            put("user", jwtUser);
+        }};
+        if (loginProperties.isSingleLogin()) {
+            // 踢掉之前已经登录的token
+            onlineUserService.kickOutForUsername(authUser.getUsername());
+        }
+        // 保存在线信息
+        onlineUserService.save(jwtUser, token, request);
+        // 返回登录信息
+        return ResponseEntity.ok(authInfo);
+    }
+
+    @ApiOperation("获取用户信息")
+    @GetMapping(value = "/info")
+    public ResponseEntity<UserDetails> getUserInfo() {
+        JwtUserDto jwtUser = (JwtUserDto) SecurityUtils.getCurrentUser();
+        return ResponseEntity.ok(jwtUser);
+    }
+
+    @ApiOperation("获取验证码")
+    @AnonymousGetMapping(value = "/code")
+    public ResponseEntity<Object> getCode() {
+        // 获取运算的结果
+        Captcha captcha = captchaConfig.getCaptcha();
+        String uuid = properties.getCodeKey() + IdUtil.simpleUUID();
+        //当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型
+        String captchaValue = captcha.text();
+        if (captcha.getCharType() - 1 == LoginCodeEnum.ARITHMETIC.ordinal() && captchaValue.contains(".")) {
+            captchaValue = captchaValue.split("\\.")[0];
+        }
+        // 保存
+        redisUtils.set(uuid, captchaValue, captchaConfig.getExpiration(), TimeUnit.MINUTES);
+        // 验证码信息
+        Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
+            put("img", captcha.toBase64());
+            put("uuid", uuid);
+        }};
+        return ResponseEntity.ok(imgResult);
+    }
+
+    @ApiOperation("退出登录")
+    @AnonymousDeleteMapping(value = "/logout")
+    public ResponseEntity<Object> logout(HttpServletRequest request) {
+        String token = tokenProvider.getToken(request);
+        onlineUserService.logout(token);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/rest/OnlineController.java b/oying-system/src/main/java/com/oying/modules/security/rest/OnlineController.java
new file mode 100644
index 0000000..c3b988c
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/rest/OnlineController.java
@@ -0,0 +1,55 @@
+package com.oying.modules.security.rest;
+
+import com.oying.modules.security.service.OnlineUserService;
+import com.oying.modules.security.service.dto.OnlineUserDto;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import com.oying.utils.EncryptUtils;
+import com.oying.utils.PageResult;
+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;
+import java.util.Set;
+
+/**
+ * @author Z
+ */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/auth/online")
+@Api(tags = "系统:在线用户管理")
+public class OnlineController {
+
+    private final OnlineUserService onlineUserService;
+
+    @ApiOperation("查询在线用户")
+    @GetMapping
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<PageResult<OnlineUserDto>> queryOnlineUser(String username, Pageable pageable){
+        return new ResponseEntity<>(onlineUserService.getAll(username, pageable),HttpStatus.OK);
+    }
+
+    @ApiOperation("导出数据")
+    @GetMapping(value = "/download")
+    @PreAuthorize("@el.check()")
+    public void exportOnlineUser(HttpServletResponse response, String username) throws IOException {
+        onlineUserService.download(onlineUserService.getAll(username), response);
+    }
+
+    @ApiOperation("踢出用户")
+    @DeleteMapping
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<Object> deleteOnlineUser(@RequestBody Set<String> keys) throws Exception {
+        for (String token : keys) {
+            // 解密Key
+            token = EncryptUtils.desDecrypt(token);
+            onlineUserService.logout(token);
+        }
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/security/JwtAccessDeniedHandler.java b/oying-system/src/main/java/com/oying/modules/security/security/JwtAccessDeniedHandler.java
new file mode 100644
index 0000000..51cb1e8
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/security/JwtAccessDeniedHandler.java
@@ -0,0 +1,28 @@
+package com.oying.modules.security.security;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.oying.exception.handler.ApiError;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.stereotype.Component;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Z
+ */
+@Component
+public class JwtAccessDeniedHandler implements AccessDeniedHandler {
+
+   @Override
+   public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
+      //当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应
+      response.setStatus(HttpStatus.FORBIDDEN.value());
+      response.setContentType("application/json;charset=UTF-8");
+      ObjectMapper objectMapper = new ObjectMapper();
+      String jsonResponse = objectMapper.writeValueAsString(ApiError.error(HttpStatus.FORBIDDEN.value(), "禁止访问,您没有权限访问此资源"));
+      response.getWriter().write(jsonResponse);
+   }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/security/JwtAuthenticationEntryPoint.java b/oying-system/src/main/java/com/oying/modules/security/security/JwtAuthenticationEntryPoint.java
new file mode 100644
index 0000000..2b3ca1b
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/security/JwtAuthenticationEntryPoint.java
@@ -0,0 +1,31 @@
+package com.oying.modules.security.security;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.exception.handler.ApiError;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author Z
+ */
+@Slf4j
+@Component
+public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
+        // 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应
+        int code = HttpStatus.UNAUTHORIZED.value();
+        response.setStatus(code);
+        response.setContentType("application/json;charset=UTF-8");
+        ObjectMapper objectMapper = new ObjectMapper();
+        String jsonResponse = objectMapper.writeValueAsString(ApiError.error(HttpStatus.UNAUTHORIZED.value(), "登录状态已过期,请重新登录"));
+        response.getWriter().write(jsonResponse);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/security/TokenConfigurer.java b/oying-system/src/main/java/com/oying/modules/security/security/TokenConfigurer.java
new file mode 100644
index 0000000..2097641
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/security/TokenConfigurer.java
@@ -0,0 +1,26 @@
+package com.oying.modules.security.security;
+
+import lombok.RequiredArgsConstructor;
+import com.oying.modules.security.config.SecurityProperties;
+import com.oying.modules.security.service.OnlineUserService;
+import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+/**
+ * @author Z
+ */
+@RequiredArgsConstructor
+public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
+
+    private final TokenProvider tokenProvider;
+    private final SecurityProperties properties;
+    private final OnlineUserService onlineUserService;
+
+    @Override
+    public void configure(HttpSecurity http) {
+        TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService);
+        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/security/TokenFilter.java b/oying-system/src/main/java/com/oying/modules/security/security/TokenFilter.java
new file mode 100644
index 0000000..ba51571
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/security/TokenFilter.java
@@ -0,0 +1,77 @@
+package com.oying.modules.security.security;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.modules.security.config.SecurityProperties;
+import com.oying.modules.security.service.dto.OnlineUserDto;
+import com.oying.modules.security.service.OnlineUserService;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.GenericFilterBean;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * @author Z
+ */
+@Slf4j
+public class TokenFilter extends GenericFilterBean {
+
+    private final TokenProvider tokenProvider;
+    private final SecurityProperties properties;
+    private final OnlineUserService onlineUserService;
+
+    /**
+     * @param tokenProvider     Token
+     * @param properties        JWT
+     * @param onlineUserService 用户在线
+     */
+    public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService) {
+        this.properties = properties;
+        this.onlineUserService = onlineUserService;
+        this.tokenProvider = tokenProvider;
+    }
+
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
+        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
+        String token = resolveToken(httpServletRequest);
+        // 对于 Token 为空的不需要去查 Redis
+        if(StrUtil.isNotBlank(token)){
+            // 获取用户Token的Key
+            String loginKey = tokenProvider.loginKey(token);
+            OnlineUserDto onlineUserDto = onlineUserService.getOne(loginKey);
+            // 判断用户在线信息是否为空
+            if (onlineUserDto != null) {
+                // Token 续期判断
+                tokenProvider.checkRenewal(token);
+                // 获取认证信息,设置上下文
+                Authentication authentication = tokenProvider.getAuthentication(token);
+                SecurityContextHolder.getContext().setAuthentication(authentication);
+            }
+        }
+        filterChain.doFilter(servletRequest, servletResponse);
+    }
+
+    /**
+     * 初步检测Token
+     *
+     * @param request /
+     * @return /
+     */
+    private String resolveToken(HttpServletRequest request) {
+        String bearerToken = request.getHeader(properties.getHeader());
+        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) {
+            // 去掉令牌前缀
+            return bearerToken.replace(properties.getTokenStartWith(), "");
+        } else {
+            log.debug("非法Token:{}", bearerToken);
+        }
+        return null;
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/security/TokenProvider.java b/oying-system/src/main/java/com/oying/modules/security/security/TokenProvider.java
new file mode 100644
index 0000000..ac2a790
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/security/TokenProvider.java
@@ -0,0 +1,131 @@
+package com.oying.modules.security.security;
+
+import cn.hutool.core.date.DateField;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.IdUtil;
+import com.oying.modules.security.service.dto.JwtUserDto;
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.io.Decoders;
+import io.jsonwebtoken.security.Keys;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.modules.security.config.SecurityProperties;
+import com.oying.utils.RedisUtils;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.stereotype.Component;
+import javax.servlet.http.HttpServletRequest;
+import java.security.Key;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Z
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class TokenProvider implements InitializingBean {
+
+    private JwtParser jwtParser;
+    private JwtBuilder jwtBuilder;
+    private final RedisUtils redisUtils;
+    private final SecurityProperties properties;
+    public static final String AUTHORITIES_UUID_KEY = "uid";
+    public static final String AUTHORITIES_UID_KEY = "userId";
+
+    @Override
+    public void afterPropertiesSet() {
+        byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret());
+        Key key = Keys.hmacShaKeyFor(keyBytes);
+        jwtParser = Jwts.parserBuilder()
+                .setSigningKey(key)
+                .build();
+        jwtBuilder = Jwts.builder()
+                .signWith(key, SignatureAlgorithm.HS512);
+    }
+
+    /**
+     * 创建Token 设置永不过期,
+     * Token 的时间有效性转到Redis 维护
+     * @param user /
+     * @return /
+     */
+    public String createToken(JwtUserDto user) {
+        // 设置参数
+        Map<String, Object> claims = new HashMap<>(6);
+        // 设置用户ID
+        claims.put(AUTHORITIES_UID_KEY, user.getUser().getId());
+        // 设置UUID,确保每次Token不一样
+        claims.put(AUTHORITIES_UUID_KEY, IdUtil.simpleUUID());
+        return jwtBuilder
+                .setClaims(claims)
+                .setSubject(user.getUsername())
+                .compact();
+    }
+
+    /**
+     * 依据Token 获取鉴权信息
+     *
+     * @param token /
+     * @return /
+     */
+    Authentication getAuthentication(String token) {
+        Claims claims = getClaims(token);
+        User principal = new User(claims.getSubject(), "******", new ArrayList<>());
+        return new UsernamePasswordAuthenticationToken(principal, token, new ArrayList<>());
+    }
+
+    public Claims getClaims(String token) {
+        return jwtParser
+                .parseClaimsJws(token)
+                .getBody();
+    }
+
+    /**
+     * @param token 需要检查的token
+     */
+    public void checkRenewal(String token) {
+        // 判断是否续期token,计算token的过期时间
+        String loginKey = loginKey(token);
+        long time = redisUtils.getExpire(loginKey) * 1000;
+        Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
+        // 判断当前时间与过期时间的时间差
+        long differ = expireDate.getTime() - System.currentTimeMillis();
+        // 如果在续期检查的范围内,则续期
+        if (differ <= properties.getDetect()) {
+            long renew = time + properties.getRenew();
+            redisUtils.expire(loginKey, renew, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    public String getToken(HttpServletRequest request) {
+        final String requestHeader = request.getHeader(properties.getHeader());
+        if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
+            return requestHeader.substring(7);
+        }
+        return null;
+    }
+
+    /**
+     * 获取登录用户RedisKey
+     * @param token /
+     * @return key
+     */
+    public String loginKey(String token) {
+        Claims claims = getClaims(token);
+        return properties.getOnlineKey() + claims.getSubject() + ":" + getId(token);
+    }
+
+    /**
+     * 获取会话编号
+     * @param token /
+     * @return /
+     */
+    public String getId(String token) {
+        Claims claims = getClaims(token);
+        return claims.get(AUTHORITIES_UUID_KEY, String.class);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/service/OnlineUserService.java b/oying-system/src/main/java/com/oying/modules/security/service/OnlineUserService.java
new file mode 100644
index 0000000..0945d3f
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/service/OnlineUserService.java
@@ -0,0 +1,133 @@
+package com.oying.modules.security.service;
+
+import com.oying.modules.security.security.TokenProvider;
+import com.oying.modules.security.service.dto.JwtUserDto;
+import com.oying.utils.*;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.modules.security.config.SecurityProperties;
+import com.oying.modules.security.service.dto.OnlineUserDto;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Z
+ * @date 2019年10月26日21:56:27
+ */
+@Service
+@Slf4j
+@AllArgsConstructor
+public class OnlineUserService {
+
+    private final SecurityProperties properties;
+    private final TokenProvider tokenProvider;
+    private final RedisUtils redisUtils;
+
+    /**
+     * 保存在线用户信息
+     * @param jwtUserDto /
+     * @param token /
+     * @param request /
+     */
+    public void save(JwtUserDto jwtUserDto, String token, HttpServletRequest request){
+        String dept = jwtUserDto.getUser().getDept().getName();
+        String ip = StringUtils.getIp(request);
+        String id = tokenProvider.getId(token);
+        String browser = StringUtils.getBrowser(request);
+        String address = StringUtils.getCityInfo(ip);
+        OnlineUserDto onlineUserDto = null;
+        try {
+            onlineUserDto = new OnlineUserDto(id, jwtUserDto.getUsername(), jwtUserDto.getUser().getNickName(), dept, browser , ip, address, EncryptUtils.desEncrypt(token), new Date());
+        } catch (Exception e) {
+            log.error(e.getMessage(),e);
+        }
+        String loginKey = tokenProvider.loginKey(token);
+        redisUtils.set(loginKey, onlineUserDto, properties.getTokenValidityInSeconds(), TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * 查询全部数据
+     * @param username /
+     * @param pageable /
+     * @return /
+     */
+    public PageResult<OnlineUserDto> getAll(String username, Pageable pageable){
+        List<OnlineUserDto> onlineUserDtos = getAll(username);
+        int pageNumber = pageable.getPageNumber() - 1;
+        return PageUtil.toPage(
+                PageUtil.paging(pageNumber,pageable.getPageSize(), onlineUserDtos),
+                onlineUserDtos.size()
+        );
+    }
+
+    /**
+     * 查询全部数据,不分页
+     * @param username /
+     * @return /
+     */
+    public List<OnlineUserDto> getAll(String username){
+        String loginKey = properties.getOnlineKey() +
+                (StringUtils.isBlank(username) ? "" : "*" + username);
+        List<String> keys = redisUtils.scan(loginKey + "*");
+        Collections.reverse(keys);
+        List<OnlineUserDto> onlineUserDtos = new ArrayList<>();
+        for (String key : keys) {
+            onlineUserDtos.add(redisUtils.get(key, OnlineUserDto.class));
+        }
+        onlineUserDtos.sort((o1, o2) -> o2.getLoginTime().compareTo(o1.getLoginTime()));
+        return onlineUserDtos;
+    }
+
+    /**
+     * 退出登录
+     * @param token /
+     */
+    public void logout(String token) {
+        String loginKey = tokenProvider.loginKey(token);
+        redisUtils.del(loginKey);
+    }
+
+    /**
+     * 导出
+     * @param all /
+     * @param response /
+     * @throws IOException /
+     */
+    public void download(List<OnlineUserDto> all, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (OnlineUserDto user : all) {
+            Map<String,Object> map = new LinkedHashMap<>();
+            map.put("用户名", user.getUserName());
+            map.put("机构", user.getDept());
+            map.put("登录IP", user.getIp());
+            map.put("登录地点", user.getAddress());
+            map.put("浏览器", user.getBrowser());
+            map.put("登录日期", user.getLoginTime());
+            list.add(map);
+        }
+        FileUtil.downloadExcel(list, response);
+    }
+
+    /**
+     * 查询用户
+     * @param key /
+     * @return /
+     */
+    public OnlineUserDto getOne(String key) {
+        return redisUtils.get(key, OnlineUserDto.class);
+    }
+
+    /**
+     * 根据用户名强退用户
+     * @param username /
+     */
+    public void kickOutForUsername(String username) {
+        String loginKey = properties.getOnlineKey() + username + "*";
+        redisUtils.scanDel(loginKey);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/service/UserCacheManager.java b/oying-system/src/main/java/com/oying/modules/security/service/UserCacheManager.java
new file mode 100644
index 0000000..633dac2
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/service/UserCacheManager.java
@@ -0,0 +1,70 @@
+package com.oying.modules.security.service;
+
+import cn.hutool.core.util.RandomUtil;
+import com.oying.modules.security.service.dto.JwtUserDto;
+import com.oying.modules.security.config.LoginProperties;
+import com.oying.utils.RedisUtils;
+import com.oying.utils.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+import javax.annotation.Resource;
+
+/**
+ * @author Z
+ * @description 用户缓存管理
+ * @date 2022-05-26
+ **/
+@Component
+public class UserCacheManager {
+
+    @Resource
+    private RedisUtils redisUtils;
+    @Value("${login.user-cache.idle-time}")
+    private long idleTime;
+
+    /**
+     * 返回用户缓存
+     * @param userName 用户名
+     * @return JwtUserDto
+     */
+    public JwtUserDto getUserCache(String userName) {
+        // 转小写
+        userName = StringUtils.lowerCase(userName);
+        if (StringUtils.isNotEmpty(userName)) {
+            // 获取数据
+            return redisUtils.get(LoginProperties.cacheKey + userName, JwtUserDto.class);
+        }
+        return null;
+    }
+
+    /**
+     *  添加缓存到Redis
+     * @param userName 用户名
+     */
+    @Async
+    public void addUserCache(String userName, JwtUserDto user) {
+        // 转小写
+        userName = StringUtils.lowerCase(userName);
+        if (StringUtils.isNotEmpty(userName)) {
+            // 添加数据, 避免数据同时过期
+            long time = idleTime + RandomUtil.randomInt(900, 1800);
+            redisUtils.set(LoginProperties.cacheKey + userName, user, time);
+        }
+    }
+
+    /**
+     * 清理用户缓存信息
+     * 用户信息变更时
+     * @param userName 用户名
+     */
+    @Async
+    public void cleanUserCache(String userName) {
+        // 转小写
+        userName = StringUtils.lowerCase(userName);
+        if (StringUtils.isNotEmpty(userName)) {
+            // 清除数据
+            redisUtils.del(LoginProperties.cacheKey + userName);
+        }
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/service/UserDetailsServiceImpl.java b/oying-system/src/main/java/com/oying/modules/security/service/UserDetailsServiceImpl.java
new file mode 100644
index 0000000..a9da4b2
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/service/UserDetailsServiceImpl.java
@@ -0,0 +1,50 @@
+package com.oying.modules.security.service;
+
+import com.oying.modules.security.service.dto.AuthorityDto;
+import com.oying.modules.security.service.dto.JwtUserDto;
+import com.oying.modules.system.domain.User;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.exception.BadRequestException;
+import com.oying.modules.system.service.DataService;
+import com.oying.modules.system.service.RoleService;
+import com.oying.modules.system.service.UserService;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.stereotype.Service;
+import java.util.List;
+
+/**
+ * @author Z
+ * @date 2018-11-22
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service("userDetailsService")
+public class UserDetailsServiceImpl implements UserDetailsService {
+    private final UserService userService;
+    private final RoleService roleService;
+    private final DataService dataService;
+    private final UserCacheManager userCacheManager;
+
+    @Override
+    public JwtUserDto loadUserByUsername(String username) {
+        JwtUserDto jwtUserDto = userCacheManager.getUserCache(username);
+        if(jwtUserDto == null){
+            User user = userService.getLoginData(username);
+            if (user == null) {
+                throw new BadRequestException("用户不存在");
+            } else {
+                if (!user.getEnabled()) {
+                    throw new BadRequestException("账号未激活!");
+                }
+                // 获取用户的权限
+                List<AuthorityDto> authorities = roleService.buildPermissions(user);
+                // 初始化JwtUserDto
+                jwtUserDto = new JwtUserDto(user, dataService.getDeptIds(user), authorities);
+                // 添加缓存数据
+                userCacheManager.addUserCache(username, jwtUserDto);
+            }
+        }
+        return jwtUserDto;
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/service/dto/AuthUserDto.java b/oying-system/src/main/java/com/oying/modules/security/service/dto/AuthUserDto.java
new file mode 100644
index 0000000..1acd8d2
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/service/dto/AuthUserDto.java
@@ -0,0 +1,29 @@
+package com.oying.modules.security.service.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author Z
+ * @date 2018-11-30
+ */
+@Getter
+@Setter
+public class AuthUserDto {
+
+    @NotBlank
+    @ApiModelProperty(value = "用户名")
+    private String username;
+
+    @NotBlank
+    @ApiModelProperty(value = "密码")
+    private String password;
+
+    @ApiModelProperty(value = "验证码")
+    private String code;
+
+    @ApiModelProperty(value = "验证码的key")
+    private String uuid = "";
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/service/dto/AuthorityDto.java b/oying-system/src/main/java/com/oying/modules/security/service/dto/AuthorityDto.java
new file mode 100644
index 0000000..fbdf057
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/service/dto/AuthorityDto.java
@@ -0,0 +1,21 @@
+package com.oying.modules.security.service.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+
+/**
+ * 避免序列化问题
+ * @author Z
+ * @date 2018-11-30
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class AuthorityDto implements GrantedAuthority {
+
+    @ApiModelProperty(value = "角色名")
+    private String authority;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/service/dto/JwtUserDto.java b/oying-system/src/main/java/com/oying/modules/security/service/dto/JwtUserDto.java
new file mode 100644
index 0000000..12805bd
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/service/dto/JwtUserDto.java
@@ -0,0 +1,69 @@
+package com.oying.modules.security.service.dto;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import com.oying.modules.system.domain.User;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.security.core.userdetails.UserDetails;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author Z
+ * @date 2018-11-23
+ */
+@Getter
+@AllArgsConstructor
+public class JwtUserDto implements UserDetails {
+
+    @ApiModelProperty(value = "用户")
+    private final User user;
+
+    @ApiModelProperty(value = "数据权限")
+    private final List<Long> dataScopes;
+
+    @ApiModelProperty(value = "角色")
+    private final List<AuthorityDto> authorities;
+
+    public Set<String> getRoles() {
+        return authorities.stream().map(AuthorityDto::getAuthority).collect(Collectors.toSet());
+    }
+
+    @Override
+    @JSONField(serialize = false)
+    public String getPassword() {
+        return user.getPassword();
+    }
+
+    @Override
+    @JSONField(serialize = false)
+    public String getUsername() {
+        return user.getUsername();
+    }
+
+    @JSONField(serialize = false)
+    @Override
+    public boolean isAccountNonExpired() {
+        return true;
+    }
+
+    @JSONField(serialize = false)
+    @Override
+    public boolean isAccountNonLocked() {
+        return true;
+    }
+
+    @JSONField(serialize = false)
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return true;
+    }
+
+    @Override
+    @JSONField(serialize = false)
+    public boolean isEnabled() {
+        return user.getEnabled();
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/security/service/dto/OnlineUserDto.java b/oying-system/src/main/java/com/oying/modules/security/service/dto/OnlineUserDto.java
new file mode 100644
index 0000000..8a0bdb6
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/security/service/dto/OnlineUserDto.java
@@ -0,0 +1,44 @@
+package com.oying.modules.security.service.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import java.util.Date;
+
+/**
+ * 在线用户
+ * @author Z
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class OnlineUserDto {
+
+    @ApiModelProperty(value = "Token编号")
+    private String uid;
+
+    @ApiModelProperty(value = "用户名")
+    private String userName;
+
+    @ApiModelProperty(value = "昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "岗位")
+    private String dept;
+
+    @ApiModelProperty(value = "浏览器")
+    private String browser;
+
+    @ApiModelProperty(value = "IP")
+    private String ip;
+
+    @ApiModelProperty(value = "地址")
+    private String address;
+
+    @ApiModelProperty(value = "token")
+    private String key;
+
+    @ApiModelProperty(value = "登录时间")
+    private Date loginTime;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/Dept.java b/oying-system/src/main/java/com/oying/modules/system/domain/Dept.java
new file mode 100644
index 0000000..8b3a571
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/Dept.java
@@ -0,0 +1,92 @@
+package com.oying.modules.system.domain;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import com.oying.base.BaseEntity;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+* @author Z
+* @date 2019-03-25
+*/
+@Getter
+@Setter
+@TableName("sys_dept")
+public class Dept extends BaseEntity implements Serializable {
+
+    @NotNull(groups = Update.class)
+    @TableId(value="dept_id", type = IdType.AUTO)
+    @ApiModelProperty(value = "ID", hidden = true)
+    private Long id;
+
+    @TableField(exist = false)
+    @JSONField(serialize = false)
+    @ApiModelProperty(value = "角色")
+    private Set<Role> roles;
+
+    @TableField(exist = false)
+    @ApiModelProperty(value = "子机构")
+    private List<Dept> children;
+
+    @ApiModelProperty(value = "排序")
+    private Integer deptSort;
+
+    @NotBlank
+    @ApiModelProperty(value = "机构名称")
+    private String name;
+
+    @NotNull
+    @ApiModelProperty(value = "是否启用")
+    private Boolean enabled;
+
+    @ApiModelProperty(value = "上级机构")
+    private Long pid;
+
+    @ApiModelProperty(value = "子节点数目", hidden = true)
+    private Integer subCount = 0;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        Dept dept = (Dept) o;
+        return Objects.equals(id, dept.id) &&
+                Objects.equals(name, dept.name);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, name);
+    }
+
+    @ApiModelProperty(value = "是否有子节点")
+    public Boolean getHasChildren() {
+        return subCount > 0;
+    }
+
+
+    @ApiModelProperty(value = "是否为叶子")
+    public Boolean getLeaf() {
+        return subCount <= 0;
+    }
+
+    @ApiModelProperty(value = "标签名称")
+    public String getLabel() {
+        return name;
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/Dict.java b/oying-system/src/main/java/com/oying/modules/system/domain/Dict.java
new file mode 100644
index 0000000..2daba1a
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/Dict.java
@@ -0,0 +1,34 @@
+package com.oying.modules.system.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import com.oying.base.BaseEntity;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+* @author Z
+* @date 2019-04-10
+*/
+@Getter
+@Setter
+@TableName("sys_dict")
+public class Dict extends BaseEntity implements Serializable {
+
+    @NotNull(groups = Update.class)
+    @ApiModelProperty(value = "ID", hidden = true)
+    @TableId(value = "dict_id", type = IdType.AUTO)
+    private Long id;
+
+    @NotBlank
+    @ApiModelProperty(value = "名称")
+    private String name;
+
+    @ApiModelProperty(value = "描述")
+    private String description;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/DictDetail.java b/oying-system/src/main/java/com/oying/modules/system/domain/DictDetail.java
new file mode 100644
index 0000000..101b693
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/DictDetail.java
@@ -0,0 +1,44 @@
+package com.oying.modules.system.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import com.oying.base.BaseEntity;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+* @author Z
+* @date 2019-04-10
+*/
+@Getter
+@Setter
+@TableName("sys_dict_detail")
+public class DictDetail extends BaseEntity implements Serializable {
+
+    @NotNull(groups = Update.class)
+    @ApiModelProperty(value = "ID", hidden = true)
+    @TableId(value = "detail_id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField(value = "dict_id")
+    @ApiModelProperty(hidden = true)
+    private Long dictId;
+
+    @TableField(exist = false)
+    @ApiModelProperty(value = "字典")
+    private Dict dict;
+
+    @ApiModelProperty(value = "字典标签")
+    private String label;
+
+    @ApiModelProperty(value = "字典值")
+    private String value;
+
+    @ApiModelProperty(value = "排序")
+    private Integer dictSort = 999;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/Job.java b/oying-system/src/main/java/com/oying/modules/system/domain/Job.java
new file mode 100644
index 0000000..19852ca
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/Job.java
@@ -0,0 +1,57 @@
+package com.oying.modules.system.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import com.oying.base.BaseEntity;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+* @author Z
+* @date 2019-03-29
+*/
+@Getter
+@Setter
+@TableName("sys_job")
+public class Job extends BaseEntity implements Serializable {
+
+    @NotNull(groups = Update.class)
+    @TableId(value="job_id", type = IdType.AUTO)
+    @ApiModelProperty(value = "ID", hidden = true)
+    private Long id;
+
+    @NotBlank
+    @ApiModelProperty(value = "岗位名称")
+    private String name;
+
+    @NotNull
+    @ApiModelProperty(value = "岗位排序")
+    private Long jobSort;
+
+    @NotNull
+    @ApiModelProperty(value = "是否启用")
+    private Boolean enabled;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        Job job = (Job) o;
+        return Objects.equals(id, job.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/Menu.java b/oying-system/src/main/java/com/oying/modules/system/domain/Menu.java
new file mode 100644
index 0000000..e1c777d
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/Menu.java
@@ -0,0 +1,111 @@
+package com.oying.modules.system.domain;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import com.oying.base.BaseEntity;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2018-12-17
+ */
+@Getter
+@Setter
+@TableName("sys_menu")
+public class Menu extends BaseEntity implements Serializable {
+
+    @NotNull(groups = {Update.class})
+    @TableId(value="menu_id", type = IdType.AUTO)
+    @ApiModelProperty(value = "ID", hidden = true)
+    private Long id;
+
+    @TableField(exist = false)
+    @JSONField(serialize = false)
+    @ApiModelProperty(value = "菜单角色")
+    private Set<Role> roles;
+
+    @TableField(exist = false)
+    private List<Menu> children;
+
+    @ApiModelProperty(value = "菜单标题")
+    private String title;
+
+    @TableField(value = "name")
+    @ApiModelProperty(value = "菜单组件名称")
+    private String componentName;
+
+    @ApiModelProperty(value = "排序")
+    private Integer menuSort = 999;
+
+    @ApiModelProperty(value = "组件路径")
+    private String component;
+
+    @ApiModelProperty(value = "路由地址")
+    private String path;
+
+    @ApiModelProperty(value = "菜单类型,目录、菜单、按钮")
+    private Integer type;
+
+    @ApiModelProperty(value = "权限标识")
+    private String permission;
+
+    @ApiModelProperty(value = "菜单图标")
+    private String icon;
+
+    @ApiModelProperty(value = "缓存")
+    private Boolean cache;
+
+    @ApiModelProperty(value = "是否隐藏")
+    private Boolean hidden;
+
+    @ApiModelProperty(value = "上级菜单")
+    private Long pid;
+
+    @ApiModelProperty(value = "子节点数目", hidden = true)
+    private Integer subCount = 0;
+
+    @ApiModelProperty(value = "外链菜单")
+    private Boolean iFrame;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        Menu menu = (Menu) o;
+        return Objects.equals(id, menu.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id);
+    }
+
+    @ApiModelProperty(value = "是否有子节点")
+    public Boolean getHasChildren() {
+        return subCount > 0;
+    }
+
+    @ApiModelProperty(value = "是否为叶子")
+    public Boolean getLeaf() {
+        return subCount <= 0;
+    }
+
+    @ApiModelProperty(value = "标签名称")
+    public String getLabel() {
+        return title;
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/Role.java b/oying-system/src/main/java/com/oying/modules/system/domain/Role.java
new file mode 100644
index 0000000..0071841
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/Role.java
@@ -0,0 +1,74 @@
+package com.oying.modules.system.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import com.oying.base.BaseEntity;
+import com.oying.utils.enums.DataScopeEnum;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * 角色
+ * @author Z
+ * @date 2018-11-22
+ */
+@Getter
+@Setter
+@TableName("sys_role")
+public class Role extends BaseEntity implements Serializable {
+
+    @NotNull(groups = {Update.class})
+    @TableId(value="role_id", type = IdType.AUTO)
+    @ApiModelProperty(value = "ID", hidden = true)
+    private Long id;
+
+    @TableField(exist = false)
+    @ApiModelProperty(value = "用户", hidden = true)
+    private Set<User> users;
+
+    @TableField(exist = false)
+    @ApiModelProperty(value = "菜单", hidden = true)
+    private Set<Menu> menus;
+
+    @TableField(exist = false)
+    @ApiModelProperty(value = "机构", hidden = true)
+    private Set<Dept> depts;
+
+    @NotBlank
+    @ApiModelProperty(value = "名称", hidden = true)
+    private String name;
+
+    @ApiModelProperty(value = "数据权限,全部 、 本级 、 自定义")
+    private String dataScope = DataScopeEnum.THIS_LEVEL.getValue();
+
+    @ApiModelProperty(value = "级别,数值越小,级别越大")
+    private Integer level = 3;
+
+    @ApiModelProperty(value = "描述")
+    private String description;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        Role role = (Role) o;
+        return Objects.equals(id, role.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/User.java b/oying-system/src/main/java/com/oying/modules/system/domain/User.java
new file mode 100644
index 0000000..900eb83
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/User.java
@@ -0,0 +1,105 @@
+package com.oying.modules.system.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import com.oying.base.BaseEntity;
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2018-11-22
+ */
+@Getter
+@Setter
+@TableName("sys_user")
+public class User extends BaseEntity implements Serializable {
+
+    @NotNull(groups = Update.class)
+    @TableId(value="user_id", type = IdType.AUTO)
+    @ApiModelProperty(value = "ID", hidden = true)
+    private Long id;
+
+    @TableField(exist = false)
+    @ApiModelProperty(value = "用户角色")
+    private Set<Role> roles;
+
+    @TableField(exist = false)
+    @ApiModelProperty(value = "用户岗位")
+    private Set<Job> jobs;
+
+    @TableField(value = "dept_id")
+    @ApiModelProperty(hidden = true)
+    private Long deptId;
+
+    @ApiModelProperty(value = "用户机构")
+    @TableField(exist = false)
+    private Dept dept;
+
+    @NotBlank
+    @ApiModelProperty(value = "用户名称")
+    private String username;
+
+    @NotBlank
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @Email
+    @NotBlank
+    @ApiModelProperty(value = "邮箱")
+    private String email;
+
+    @NotBlank
+    @ApiModelProperty(value = "电话号码")
+    private String phone;
+
+    @ApiModelProperty(value = "用户性别")
+    private String gender;
+
+    @ApiModelProperty(value = "头像真实名称",hidden = true)
+    private String avatarName;
+
+    @ApiModelProperty(value = "头像存储的路径", hidden = true)
+    private String avatarPath;
+
+    @ApiModelProperty(value = "密码")
+    private String password;
+
+    @NotNull
+    @ApiModelProperty(value = "是否启用")
+    private Boolean enabled;
+
+    @ApiModelProperty(value = "是否为admin账号", hidden = true)
+    private Boolean isAdmin = false;
+
+    @ApiModelProperty(value = "最后修改密码的时间", hidden = true)
+    private Date pwdResetTime;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        User user = (User) o;
+        return Objects.equals(id, user.id) &&
+                Objects.equals(username, user.username);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, username);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/dto/DeptQueryCriteria.java b/oying-system/src/main/java/com/oying/modules/system/domain/dto/DeptQueryCriteria.java
new file mode 100644
index 0000000..2266571
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/dto/DeptQueryCriteria.java
@@ -0,0 +1,32 @@
+package com.oying.modules.system.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.sql.Timestamp;
+import java.util.List;
+
+/**
+* @author Z
+* @date 2019-03-25
+*/
+@Data
+public class DeptQueryCriteria{
+
+    @ApiModelProperty(value = "机构id集合")
+    private List<Long> ids;
+
+    @ApiModelProperty(value = "机构名称")
+    private String name;
+
+    @ApiModelProperty(value = "是否启用")
+    private Boolean enabled;
+
+    @ApiModelProperty(value = "上级机构")
+    private Long pid;
+
+    @ApiModelProperty(value = "PID为空查询")
+    private Boolean pidIsNull;
+
+    @ApiModelProperty(value = "创建时间")
+    private List<Timestamp> createTime;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/dto/DictDetailQueryCriteria.java b/oying-system/src/main/java/com/oying/modules/system/domain/dto/DictDetailQueryCriteria.java
new file mode 100644
index 0000000..397891b
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/dto/DictDetailQueryCriteria.java
@@ -0,0 +1,24 @@
+package com.oying.modules.system.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+* @author Z
+* @date 2019-04-10
+*/
+@Data
+public class DictDetailQueryCriteria {
+
+    @ApiModelProperty(value = "标签")
+    private String label;
+
+    @ApiModelProperty(value = "字典名称")
+    private String dictName;
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer page = 1;
+
+    @ApiModelProperty(value = "每页数据量", example = "10")
+    private Integer size = 10;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/dto/DictQueryCriteria.java b/oying-system/src/main/java/com/oying/modules/system/domain/dto/DictQueryCriteria.java
new file mode 100644
index 0000000..0d803e7
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/dto/DictQueryCriteria.java
@@ -0,0 +1,21 @@
+package com.oying.modules.system.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @author Z
+ * 公共查询类
+ */
+@Data
+public class DictQueryCriteria {
+
+    @ApiModelProperty(value = "模糊查询")
+    private String blurry;
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer page = 1;
+
+    @ApiModelProperty(value = "每页数据量", example = "10")
+    private Integer size = 10;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/dto/JobQueryCriteria.java b/oying-system/src/main/java/com/oying/modules/system/domain/dto/JobQueryCriteria.java
new file mode 100644
index 0000000..cb52f5d
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/dto/JobQueryCriteria.java
@@ -0,0 +1,31 @@
+package com.oying.modules.system.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import java.sql.Timestamp;
+import java.util.List;
+
+/**
+* @author Z
+* @date 2019-6-4 14:49:34
+*/
+@Data
+@NoArgsConstructor
+public class JobQueryCriteria {
+
+    @ApiModelProperty(value = "岗位名称")
+    private String name;
+
+    @ApiModelProperty(value = "是否启用")
+    private Boolean enabled;
+
+    @ApiModelProperty(value = "创建时间")
+    private List<Timestamp> createTime;
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer page = 1;
+
+    @ApiModelProperty(value = "每页数据量", example = "10")
+    private Integer size = 10;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/dto/MenuMetaVo.java b/oying-system/src/main/java/com/oying/modules/system/domain/dto/MenuMetaVo.java
new file mode 100644
index 0000000..d19771c
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/dto/MenuMetaVo.java
@@ -0,0 +1,24 @@
+package com.oying.modules.system.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import java.io.Serializable;
+
+/**
+ * @author Z
+ * @date 2018-12-20
+ */
+@Data
+@AllArgsConstructor
+public class MenuMetaVo implements Serializable {
+
+    @ApiModelProperty(value = "菜单标题")
+    private String title;
+
+    @ApiModelProperty(value = "菜单图标")
+    private String icon;
+
+    @ApiModelProperty(value = "缓存")
+    private Boolean noCache;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/dto/MenuQueryCriteria.java b/oying-system/src/main/java/com/oying/modules/system/domain/dto/MenuQueryCriteria.java
new file mode 100644
index 0000000..e09f130
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/dto/MenuQueryCriteria.java
@@ -0,0 +1,26 @@
+package com.oying.modules.system.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.sql.Timestamp;
+import java.util.List;
+
+/**
+ * @author Z
+ * 公共查询类
+ */
+@Data
+public class MenuQueryCriteria {
+
+    @ApiModelProperty(value = "模糊查询")
+    private String blurry;
+
+    @ApiModelProperty(value = "创建时间")
+    private List<Timestamp> createTime;
+
+    @ApiModelProperty(value = "PID为空查询")
+    private Boolean pidIsNull;
+
+    @ApiModelProperty(value = "PID")
+    private Long pid;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/dto/MenuVo.java b/oying-system/src/main/java/com/oying/modules/system/domain/dto/MenuVo.java
new file mode 100644
index 0000000..03ca137
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/dto/MenuVo.java
@@ -0,0 +1,39 @@
+package com.oying.modules.system.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 构建前端路由时用到
+ * @author Z
+ * @date 2018-12-20
+ */
+@Data
+public class MenuVo implements Serializable {
+
+    @ApiModelProperty(value = "菜单名称")
+    private String name;
+
+    @ApiModelProperty(value = "路径")
+    private String path;
+
+    @ApiModelProperty(value = "隐藏状态")
+    private Boolean hidden;
+
+    @ApiModelProperty(value = "重定向")
+    private String redirect;
+
+    @ApiModelProperty(value = "组件")
+    private String component;
+
+    @ApiModelProperty(value = "总是显示")
+    private Boolean alwaysShow;
+
+    @ApiModelProperty(value = "元数据")
+    private MenuMetaVo meta;
+
+    @ApiModelProperty(value = "子路由")
+    private List<MenuVo> children;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/dto/RoleQueryCriteria.java b/oying-system/src/main/java/com/oying/modules/system/domain/dto/RoleQueryCriteria.java
new file mode 100644
index 0000000..17d37ce
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/dto/RoleQueryCriteria.java
@@ -0,0 +1,29 @@
+package com.oying.modules.system.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.sql.Timestamp;
+import java.util.List;
+
+/**
+ * @author Z
+ * 公共查询类
+ */
+@Data
+public class RoleQueryCriteria {
+
+    @ApiModelProperty(value = "模糊查询")
+    private String blurry;
+
+    @ApiModelProperty(value = "创建时间")
+    private List<Timestamp> createTime;
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer page = 1;
+
+    @ApiModelProperty(value = "每页数据量", example = "10")
+    private Integer size = 10;
+
+    @ApiModelProperty(value = "偏移量", hidden = true)
+    private long offset;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/dto/UserPassVo.java b/oying-system/src/main/java/com/oying/modules/system/domain/dto/UserPassVo.java
new file mode 100644
index 0000000..34bc75b
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/dto/UserPassVo.java
@@ -0,0 +1,19 @@
+package com.oying.modules.system.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 修改密码的 Vo 类
+ * @author Z
+ * @date 2019年7月11日13:59:49
+ */
+@Data
+public class UserPassVo {
+
+    @ApiModelProperty(value = "旧密码")
+    private String oldPass;
+
+    @ApiModelProperty(value = "新密码")
+    private String newPass;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/domain/dto/UserQueryCriteria.java b/oying-system/src/main/java/com/oying/modules/system/domain/dto/UserQueryCriteria.java
new file mode 100644
index 0000000..81b91f4
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/domain/dto/UserQueryCriteria.java
@@ -0,0 +1,44 @@
+package com.oying.modules.system.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.io.Serializable;
+import java.sql.Timestamp;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2018-11-23
+ */
+@Data
+public class UserQueryCriteria implements Serializable {
+
+    @ApiModelProperty(value = "ID")
+    private Long id;
+
+    @ApiModelProperty(value = "多个ID")
+    private Set<Long> deptIds = new HashSet<>();
+
+    @ApiModelProperty(value = "模糊查询")
+    private String blurry;
+
+    @ApiModelProperty(value = "是否启用")
+    private Boolean enabled;
+
+    @ApiModelProperty(value = "机构ID")
+    private Long deptId;
+
+    @ApiModelProperty(value = "创建时间")
+    private List<Timestamp> createTime;
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer page = 1;
+
+    @ApiModelProperty(value = "每页数据量", example = "10")
+    private Integer size = 10;
+
+    @ApiModelProperty(value = "偏移量", hidden = true)
+    private long offset;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/mapper/DeptMapper.java b/oying-system/src/main/java/com/oying/modules/system/mapper/DeptMapper.java
new file mode 100644
index 0000000..fa57430
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/mapper/DeptMapper.java
@@ -0,0 +1,32 @@
+package com.oying.modules.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.oying.modules.system.domain.Dept;
+import com.oying.modules.system.domain.dto.DeptQueryCriteria;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import java.util.List;
+import java.util.Set;
+
+/**
+* @author Z
+* @date 2023-06-20
+*/
+@Mapper
+public interface DeptMapper extends BaseMapper<Dept> {
+
+    List<Dept> findAll(@Param("criteria") DeptQueryCriteria criteria);
+
+    List<Dept> findByPid(@Param("pid") Long pid);
+
+    List<Dept> findByPidIsNull();
+
+    Set<Dept> findByRoleId(@Param("roleId") Long roleId);
+
+    @Select("select count(*) from sys_dept where pid = #{pid}")
+    int countByPid(@Param("pid") Long pid);
+
+    @Select("update sys_dept set sub_count = #{count} where dept_id = #{id}")
+    void updateSubCntById(@Param("count") Integer count, @Param("id") Long id);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/mapper/DictDetailMapper.java b/oying-system/src/main/java/com/oying/modules/system/mapper/DictDetailMapper.java
new file mode 100644
index 0000000..c3770de
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/mapper/DictDetailMapper.java
@@ -0,0 +1,25 @@
+package com.oying.modules.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.modules.system.domain.DictDetail;
+import com.oying.modules.system.domain.dto.DictDetailQueryCriteria;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+import java.util.Set;
+
+/**
+* @author Z
+* @date 2023-06-19
+*/
+@Mapper
+public interface DictDetailMapper extends BaseMapper<DictDetail> {
+
+    List<DictDetail> findByDictName(@Param("name") String name);
+
+    IPage<DictDetail> findAll(@Param("criteria") DictDetailQueryCriteria criteria, Page<Object> page);
+
+    void deleteByDictBatchIds(@Param("dictIds") Set<Long> dictIds);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/mapper/DictMapper.java b/oying-system/src/main/java/com/oying/modules/system/mapper/DictMapper.java
new file mode 100644
index 0000000..5d4f696
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/mapper/DictMapper.java
@@ -0,0 +1,22 @@
+package com.oying.modules.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.modules.system.domain.Dict;
+import com.oying.modules.system.domain.dto.DictQueryCriteria;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+/**
+* @author Z
+* @date 2023-06-19
+*/
+@Mapper
+public interface DictMapper extends BaseMapper<Dict> {
+
+    IPage<Dict> findAll(@Param("criteria") DictQueryCriteria criteria, Page<Object> page);
+
+    List<Dict> findAll(@Param("criteria") DictQueryCriteria criteria);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/mapper/JobMapper.java b/oying-system/src/main/java/com/oying/modules/system/mapper/JobMapper.java
new file mode 100644
index 0000000..fb4f22e
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/mapper/JobMapper.java
@@ -0,0 +1,26 @@
+package com.oying.modules.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.modules.system.domain.Job;
+import com.oying.modules.system.domain.dto.JobQueryCriteria;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import java.util.List;
+
+/**
+* @author Z
+* @date 2023-06-20
+*/
+@Mapper
+public interface JobMapper extends BaseMapper<Job> {
+
+    @Select("select job_id as id from sys_job where name = #{name}")
+    Job findByName(@Param("name") String name);
+
+    List<Job> findAll(@Param("criteria") JobQueryCriteria criteria);
+
+    IPage<Job> findAll(@Param("criteria") JobQueryCriteria criteria, Page<Object> page);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/mapper/MenuMapper.java b/oying-system/src/main/java/com/oying/modules/system/mapper/MenuMapper.java
new file mode 100644
index 0000000..56fc038
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/mapper/MenuMapper.java
@@ -0,0 +1,39 @@
+package com.oying.modules.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.oying.modules.system.domain.Menu;
+import com.oying.modules.system.domain.dto.MenuQueryCriteria;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2023-06-20
+ */
+@Mapper
+public interface MenuMapper extends BaseMapper<Menu> {
+
+    List<Menu> findAll(@Param("criteria") MenuQueryCriteria criteria);
+
+    LinkedHashSet<Menu> findByRoleIdsAndTypeNot(@Param("roleIds") Set<Long> roleIds, @Param("type") Integer type);
+
+    List<Menu> findByPidIsNullOrderByMenuSort();
+
+    List<Menu> findByPidOrderByMenuSort(@Param("pid") Long pid);
+
+    @Select("SELECT menu_id id FROM sys_menu WHERE title = #{title}")
+    Menu findByTitle(@Param("title") String title);
+
+    @Select("SELECT menu_id id FROM sys_menu WHERE name = #{name}")
+    Menu findByComponentName(@Param("name") String name);
+
+    @Select("SELECT count(*) FROM sys_menu WHERE pid = #{pid}")
+    int countByPid(@Param("pid") Long pid);
+
+    @Select("update sys_menu set sub_count = #{count} where menu_id = #{menuId} ")
+    void updateSubCntById(@Param("count") int count, @Param("menuId") Long menuId);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/mapper/RoleDeptMapper.java b/oying-system/src/main/java/com/oying/modules/system/mapper/RoleDeptMapper.java
new file mode 100644
index 0000000..a8200ad
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/mapper/RoleDeptMapper.java
@@ -0,0 +1,19 @@
+package com.oying.modules.system.mapper;
+
+import com.oying.modules.system.domain.Dept;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2023-06-20
+ */
+@Mapper
+public interface RoleDeptMapper {
+    void insertData(@Param("roleId") Long roleId, @Param("depts") Set<Dept> depts);
+
+    void deleteByRoleId(@Param("roleId") Long roleId);
+
+    void deleteByRoleIds(@Param("roleIds") Set<Long> roleIds);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/mapper/RoleMapper.java b/oying-system/src/main/java/com/oying/modules/system/mapper/RoleMapper.java
new file mode 100644
index 0000000..ba5455f
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/mapper/RoleMapper.java
@@ -0,0 +1,37 @@
+package com.oying.modules.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.oying.modules.system.domain.Role;
+import com.oying.modules.system.domain.dto.RoleQueryCriteria;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2023-06-20
+ */
+@Mapper
+public interface RoleMapper extends BaseMapper<Role> {
+
+    List<Role> queryAll();
+
+    Long countAll(@Param("criteria") RoleQueryCriteria criteria);
+
+    List<Role> findAll(@Param("criteria") RoleQueryCriteria criteria);
+
+    Role findById(@Param("roleId") Long roleId);
+
+    Role findByName(@Param("name") String name);
+
+    List<Role> findByUserId(@Param("userId") Long userId);
+
+    int countByDepts(@Param("deptIds") Set<Long> deptIds);
+
+    @Select("SELECT role.role_id as id FROM sys_role role, sys_roles_menus rm " +
+            "WHERE role.role_id = rm.role_id AND rm.menu_id = #{menuId}")
+    List<Role> findByMenuId(@Param("menuId") Long menuId);
+
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/mapper/RoleMenuMapper.java b/oying-system/src/main/java/com/oying/modules/system/mapper/RoleMenuMapper.java
new file mode 100644
index 0000000..07493ae
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/mapper/RoleMenuMapper.java
@@ -0,0 +1,21 @@
+package com.oying.modules.system.mapper;
+
+import com.oying.modules.system.domain.Menu;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2023-06-20
+ */
+@Mapper
+public interface RoleMenuMapper {
+    void insertData(@Param("roleId") Long roleId, @Param("menus") Set<Menu> menus);
+
+    void deleteByRoleId(@Param("roleId") Long roleId);
+
+    void deleteByRoleIds(@Param("roleIds") Set<Long> roleIds);
+
+    void deleteByMenuId(@Param("menuId") Long menuId);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/mapper/UserJobMapper.java b/oying-system/src/main/java/com/oying/modules/system/mapper/UserJobMapper.java
new file mode 100644
index 0000000..f61ab90
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/mapper/UserJobMapper.java
@@ -0,0 +1,20 @@
+package com.oying.modules.system.mapper;
+
+import com.oying.modules.system.domain.Job;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2023-06-20
+ */
+@Mapper
+public interface UserJobMapper {
+
+    void insertData(@Param("userId") Long userId, @Param("jobs") Set<Job> jobs);
+
+    void deleteByUserId(@Param("userId") Long userId);
+
+    void deleteByUserIds(@Param("userIds") Set<Long> userIds);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/mapper/UserMapper.java b/oying-system/src/main/java/com/oying/modules/system/mapper/UserMapper.java
new file mode 100644
index 0000000..d506a8c
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/mapper/UserMapper.java
@@ -0,0 +1,53 @@
+package com.oying.modules.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.modules.system.domain.User;
+import com.oying.modules.system.domain.dto.UserQueryCriteria;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2023-06-20
+ */
+@Mapper
+public interface UserMapper extends BaseMapper<User> {
+
+    Long countAll(@Param("criteria") UserQueryCriteria criteria);
+
+    List<User> findAll(@Param("criteria") UserQueryCriteria criteria);
+
+    IPage<User> findAll(@Param("criteria") UserQueryCriteria criteria, Page<Object> page);
+
+    User findByUsername(@Param("username") String username);
+
+    User findByEmail(@Param("email") String email);
+
+    User findByPhone(@Param("phone") String phone);
+
+    @Select("update sys_user set password = #{password} , pwd_reset_time = #{lastPasswordResetTime} where username = #{username}")
+    void updatePass(@Param("username") String username, @Param("password") String password, @Param("lastPasswordResetTime") Date lastPasswordResetTime);
+
+    @Select("update sys_user set email = #{email} where username = #{username}")
+    void updateEmail(@Param("username") String username, @Param("email") String email);
+
+    List<User> findByRoleId(@Param("roleId") Long roleId);
+
+    List<User> findByRoleDeptId(@Param("deptId") Long deptId);
+
+    List<User> findByMenuId(@Param("menuId") Long menuId);
+
+    int countByJobs(@Param("jobIds") Set<Long> jobIds);
+
+    int countByDepts(@Param("deptIds") Set<Long> deptIds);
+
+    int countByRoles(@Param("roleIds") Set<Long> roleIds);
+
+    void resetPwd(@Param("userIds") Set<Long> userIds, @Param("pwd") String pwd);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/mapper/UserRoleMapper.java b/oying-system/src/main/java/com/oying/modules/system/mapper/UserRoleMapper.java
new file mode 100644
index 0000000..63968c6
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/mapper/UserRoleMapper.java
@@ -0,0 +1,19 @@
+package com.oying.modules.system.mapper;
+
+import com.oying.modules.system.domain.Role;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2023-06-20
+ */
+@Mapper
+public interface UserRoleMapper {
+    void insertData(@Param("userId") Long userId, @Param("roles") Set<Role> roles);
+
+    void deleteByUserId(@Param("userId") Long userId);
+
+    void deleteByUserIds(@Param("userIds") Set<Long> userIds);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/rest/DeptController.java b/oying-system/src/main/java/com/oying/modules/system/rest/DeptController.java
new file mode 100644
index 0000000..3245954
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/rest/DeptController.java
@@ -0,0 +1,112 @@
+package com.oying.modules.system.rest;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.oying.modules.system.domain.Dept;
+import com.oying.modules.system.domain.dto.DeptQueryCriteria;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import com.oying.annotation.Log;
+import com.oying.exception.BadRequestException;
+import com.oying.modules.system.service.DeptService;
+import com.oying.utils.PageResult;
+import com.oying.utils.PageUtil;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import javax.servlet.http.HttpServletResponse;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+* @author Z
+* @date 2019-03-25
+*/
+@RestController
+@RequiredArgsConstructor
+@Api(tags = "系统:机构管理")
+@RequestMapping("/api/dept")
+public class DeptController {
+
+    private final DeptService deptService;
+    private static final String ENTITY_NAME = "dept";
+
+    @ApiOperation("导出机构数据")
+    @GetMapping(value = "/download")
+    @PreAuthorize("@el.check('dept:list')")
+    public void exportDept(HttpServletResponse response, DeptQueryCriteria criteria) throws Exception {
+        deptService.download(deptService.queryAll(criteria, false), response);
+    }
+
+    @ApiOperation("查询机构")
+    @GetMapping
+    @PreAuthorize("@el.check('user:list','dept:list')")
+    public ResponseEntity<PageResult<Dept>> queryDept(DeptQueryCriteria criteria) throws Exception {
+        List<Dept> depts = deptService.queryAll(criteria, true);
+        return new ResponseEntity<>(PageUtil.toPage(depts),HttpStatus.OK);
+    }
+
+    @ApiOperation("查询机构:根据ID获取同级与上级数据")
+    @PostMapping("/superior")
+    @PreAuthorize("@el.check('user:list','dept:list')")
+    public ResponseEntity<Object> getDeptSuperior(@RequestBody List<Long> ids, @RequestParam(defaultValue = "false") Boolean exclude) {
+        Set<Dept> deptSet  = new LinkedHashSet<>();
+        for (Long id : ids) {
+            Dept dept = deptService.findById(id);
+            List<Dept> depts = deptService.getSuperior(dept, new ArrayList<>());
+            if(exclude){
+                for (Dept data : depts) {
+                    if(data.getId().equals(dept.getPid())) {
+                        data.setSubCount(data.getSubCount() - 1);
+                    }
+                }
+                // 编辑机构时不显示自己以及自己下级的数据,避免出现PID数据环形问题
+                depts = depts.stream().filter(i -> !ids.contains(i.getId())).collect(Collectors.toList());
+            }
+            deptSet.addAll(depts);
+        }
+        return new ResponseEntity<>(deptService.buildTree(new ArrayList<>(deptSet)),HttpStatus.OK);
+    }
+
+    @Log("新增机构")
+    @ApiOperation("新增机构")
+    @PostMapping
+    @PreAuthorize("@el.check('dept:add')")
+    public ResponseEntity<Object> createDept(@Validated @RequestBody Dept resources){
+        if (resources.getId() != null) {
+            throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID");
+        }
+        deptService.create(resources);
+        return new ResponseEntity<>(HttpStatus.CREATED);
+    }
+
+    @Log("修改机构")
+    @ApiOperation("修改机构")
+    @PutMapping
+    @PreAuthorize("@el.check('dept:edit')")
+    public ResponseEntity<Object> updateDept(@Validated(Dept.Update.class) @RequestBody Dept resources){
+        deptService.update(resources);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Log("删除机构")
+    @ApiOperation("删除机构")
+    @DeleteMapping
+    @PreAuthorize("@el.check('dept:del')")
+    public ResponseEntity<Object> deleteDept(@RequestBody Set<Long> ids){
+        Set<Dept> depts = new HashSet<>();
+        for (Long id : ids) {
+            List<Dept> deptList = deptService.findByPid(id);
+            depts.add(deptService.findById(id));
+            if(CollectionUtil.isNotEmpty(deptList)){
+                depts = deptService.getDeleteDepts(deptList, depts);
+            }
+        }
+        // 验证是否被角色或用户关联
+        deptService.verification(depts);
+        deptService.delete(depts);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/rest/DictController.java b/oying-system/src/main/java/com/oying/modules/system/rest/DictController.java
new file mode 100644
index 0000000..48685d8
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/rest/DictController.java
@@ -0,0 +1,87 @@
+package com.oying.modules.system.rest;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.modules.system.domain.Dict;
+import com.oying.modules.system.domain.dto.DictQueryCriteria;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import com.oying.annotation.Log;
+import com.oying.exception.BadRequestException;
+import com.oying.modules.system.service.DictService;
+import com.oying.utils.PageResult;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/**
+* @author Z
+* @date 2019-04-10
+*/
+@RestController
+@RequiredArgsConstructor
+@Api(tags = "系统:字典管理")
+@RequestMapping("/api/dict")
+public class DictController {
+
+    private final DictService dictService;
+    private static final String ENTITY_NAME = "dict";
+
+    @ApiOperation("导出字典数据")
+    @GetMapping(value = "/download")
+    @PreAuthorize("@el.check('dict:list')")
+    public void exportDict(HttpServletResponse response, DictQueryCriteria criteria) throws IOException {
+        dictService.download(dictService.queryAll(criteria), response);
+    }
+
+    @ApiOperation("查询字典")
+    @GetMapping(value = "/all")
+    @PreAuthorize("@el.check('dict:list')")
+    public ResponseEntity<List<Dict>> queryAllDict(){
+        return new ResponseEntity<>(dictService.queryAll(new DictQueryCriteria()),HttpStatus.OK);
+    }
+
+    @ApiOperation("查询字典")
+    @GetMapping
+    @PreAuthorize("@el.check('dict:list')")
+    public ResponseEntity<PageResult<Dict>> queryDict(DictQueryCriteria criteria){
+        Page<Object> page = new Page<>(criteria.getPage(), criteria.getSize());
+        return new ResponseEntity<>(dictService.queryAll(criteria, page),HttpStatus.OK);
+    }
+
+    @Log("新增字典")
+    @ApiOperation("新增字典")
+    @PostMapping
+    @PreAuthorize("@el.check('dict:add')")
+    public ResponseEntity<Object> createDict(@Validated @RequestBody Dict resources){
+        if (resources.getId() != null) {
+            throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID");
+        }
+        dictService.create(resources);
+        return new ResponseEntity<>(HttpStatus.CREATED);
+    }
+
+    @Log("修改字典")
+    @ApiOperation("修改字典")
+    @PutMapping
+    @PreAuthorize("@el.check('dict:edit')")
+    public ResponseEntity<Object> updateDict(@Validated(Dict.Update.class) @RequestBody Dict resources){
+        dictService.update(resources);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Log("删除字典")
+    @ApiOperation("删除字典")
+    @DeleteMapping
+    @PreAuthorize("@el.check('dict:del')")
+    public ResponseEntity<Object> deleteDict(@RequestBody Set<Long> ids){
+        dictService.delete(ids);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/rest/DictDetailController.java b/oying-system/src/main/java/com/oying/modules/system/rest/DictDetailController.java
new file mode 100644
index 0000000..693aaed
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/rest/DictDetailController.java
@@ -0,0 +1,82 @@
+package com.oying.modules.system.rest;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.modules.system.domain.DictDetail;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import com.oying.annotation.Log;
+import com.oying.exception.BadRequestException;
+import com.oying.modules.system.service.DictDetailService;
+import com.oying.modules.system.domain.dto.DictDetailQueryCriteria;
+import com.oying.utils.PageResult;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+* @author Z
+* @date 2019-04-10
+*/
+@RestController
+@RequiredArgsConstructor
+@Api(tags = "系统:字典详情管理")
+@RequestMapping("/api/dictDetail")
+public class DictDetailController {
+
+    private final DictDetailService dictDetailService;
+    private static final String ENTITY_NAME = "dictDetail";
+
+    @ApiOperation("查询字典详情")
+    @GetMapping
+    public ResponseEntity<PageResult<DictDetail>> queryDictDetail(DictDetailQueryCriteria criteria){
+        Page<Object> page = new Page<>(criteria.getPage(), criteria.getSize());
+        return new ResponseEntity<>(dictDetailService.queryAll(criteria, page),HttpStatus.OK);
+    }
+
+    @ApiOperation("查询多个字典详情")
+    @GetMapping(value = "/map")
+    public ResponseEntity<Object> getDictDetailMaps(@RequestParam String dictName){
+        String[] names = dictName.split("[,,]");
+        Map<String, List<DictDetail>> dictMap = new HashMap<>(16);
+        for (String name : names) {
+            dictMap.put(name, dictDetailService.getDictByName(name));
+        }
+        return new ResponseEntity<>(dictMap, HttpStatus.OK);
+    }
+
+    @Log("新增字典详情")
+    @ApiOperation("新增字典详情")
+    @PostMapping
+    @PreAuthorize("@el.check('dict:add')")
+    public ResponseEntity<Object> createDictDetail(@Validated @RequestBody DictDetail resources){
+        if (resources.getId() != null) {
+            throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID");
+        }
+        dictDetailService.create(resources);
+        return new ResponseEntity<>(HttpStatus.CREATED);
+    }
+
+    @Log("修改字典详情")
+    @ApiOperation("修改字典详情")
+    @PutMapping
+    @PreAuthorize("@el.check('dict:edit')")
+    public ResponseEntity<Object> updateDictDetail(@Validated(DictDetail.Update.class) @RequestBody DictDetail resources){
+        dictDetailService.update(resources);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Log("删除字典详情")
+    @ApiOperation("删除字典详情")
+    @DeleteMapping(value = "/{id}")
+    @PreAuthorize("@el.check('dict:del')")
+    public ResponseEntity<Object> deleteDictDetail(@PathVariable Long id){
+        dictDetailService.delete(id);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/rest/JobController.java b/oying-system/src/main/java/com/oying/modules/system/rest/JobController.java
new file mode 100644
index 0000000..1638bee
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/rest/JobController.java
@@ -0,0 +1,81 @@
+package com.oying.modules.system.rest;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.modules.system.domain.Job;
+import com.oying.modules.system.domain.dto.JobQueryCriteria;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import com.oying.annotation.Log;
+import com.oying.exception.BadRequestException;
+import com.oying.modules.system.service.JobService;
+import com.oying.utils.PageResult;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Set;
+
+/**
+* @author Z
+* @date 2019-03-29
+*/
+@RestController
+@RequiredArgsConstructor
+@Api(tags = "系统:岗位管理")
+@RequestMapping("/api/job")
+public class JobController {
+
+    private final JobService jobService;
+    private static final String ENTITY_NAME = "job";
+
+    @ApiOperation("导出岗位数据")
+    @GetMapping(value = "/download")
+    @PreAuthorize("@el.check('job:list')")
+    public void exportJob(HttpServletResponse response, JobQueryCriteria criteria) throws IOException {
+        jobService.download(jobService.queryAll(criteria), response);
+    }
+
+    @ApiOperation("查询岗位")
+    @GetMapping
+    @PreAuthorize("@el.check('job:list','user:list')")
+    public ResponseEntity<PageResult<Job>> queryJob(JobQueryCriteria criteria){
+        Page<Object> page = new Page<>(criteria.getPage(), criteria.getSize());
+        return new ResponseEntity<>(jobService.queryAll(criteria, page),HttpStatus.OK);
+    }
+
+    @Log("新增岗位")
+    @ApiOperation("新增岗位")
+    @PostMapping
+    @PreAuthorize("@el.check('job:add')")
+    public ResponseEntity<Object> createJob(@Validated @RequestBody Job resources){
+        if (resources.getId() != null) {
+            throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID");
+        }
+        jobService.create(resources);
+        return new ResponseEntity<>(HttpStatus.CREATED);
+    }
+
+    @Log("修改岗位")
+    @ApiOperation("修改岗位")
+    @PutMapping
+    @PreAuthorize("@el.check('job:edit')")
+    public ResponseEntity<Object> updateJob(@Validated(Job.Update.class) @RequestBody Job resources){
+        jobService.update(resources);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Log("删除岗位")
+    @ApiOperation("删除岗位")
+    @DeleteMapping
+    @PreAuthorize("@el.check('job:del')")
+    public ResponseEntity<Object> deleteJob(@RequestBody Set<Long> ids){
+        // 验证是否被用户关联
+        jobService.verification(ids);
+        jobService.delete(ids);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/rest/LimitController.java b/oying-system/src/main/java/com/oying/modules/system/rest/LimitController.java
new file mode 100644
index 0000000..00122d5
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/rest/LimitController.java
@@ -0,0 +1,32 @@
+package com.oying.modules.system.rest;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import com.oying.annotation.Limit;
+import com.oying.annotation.rest.AnonymousGetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author Z
+ * 接口限流测试类
+ */
+@RestController
+@RequestMapping("/api/limit")
+@Api(tags = "系统:限流测试管理")
+public class LimitController {
+
+    private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger();
+
+    /**
+     * 测试限流注解,下面配置说明该接口 60秒内最多只能访问 10次,保存到redis的键名为 limit_test,
+     */
+    @AnonymousGetMapping
+    @ApiOperation("测试")
+    @Limit(key = "test", period = 60, count = 10, name = "testLimit", prefix = "limit")
+    public int testLimit() {
+        return ATOMIC_INTEGER.incrementAndGet();
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/rest/MenuController.java b/oying-system/src/main/java/com/oying/modules/system/rest/MenuController.java
new file mode 100644
index 0000000..3ee9b2a
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/rest/MenuController.java
@@ -0,0 +1,138 @@
+package com.oying.modules.system.rest;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.oying.modules.system.domain.Menu;
+import com.oying.modules.system.domain.dto.MenuQueryCriteria;
+import com.oying.modules.system.domain.dto.MenuVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import com.oying.annotation.Log;
+import com.oying.exception.BadRequestException;
+import com.oying.modules.system.service.MenuService;
+import com.oying.utils.PageResult;
+import com.oying.utils.PageUtil;
+import com.oying.utils.SecurityUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import javax.servlet.http.HttpServletResponse;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author Z
+ * @date 2018-12-03
+ */
+@RestController
+@RequiredArgsConstructor
+@Api(tags = "系统:菜单管理")
+@RequestMapping("/api/menus")
+public class MenuController {
+
+    private final MenuService menuService;
+    private static final String ENTITY_NAME = "menu";
+
+    @ApiOperation("导出菜单数据")
+    @GetMapping(value = "/download")
+    @PreAuthorize("@el.check('menu:list')")
+    public void exportMenu(HttpServletResponse response, MenuQueryCriteria criteria) throws Exception {
+        menuService.download(menuService.queryAll(criteria, false), response);
+    }
+
+    @GetMapping(value = "/build")
+    @ApiOperation("获取前端所需菜单")
+    public ResponseEntity<List<MenuVo>> buildMenus(){
+        List<Menu> menuList = menuService.findByUser(SecurityUtils.getCurrentUserId());
+        List<Menu> menus = menuService.buildTree(menuList);
+        return new ResponseEntity<>(menuService.buildMenus(menus),HttpStatus.OK);
+    }
+
+    @ApiOperation("返回全部的菜单")
+    @GetMapping(value = "/lazy")
+    @PreAuthorize("@el.check('menu:list','roles:list')")
+    public ResponseEntity<List<Menu>> queryAllMenu(@RequestParam Long pid){
+        return new ResponseEntity<>(menuService.getMenus(pid),HttpStatus.OK);
+    }
+
+    @ApiOperation("根据菜单ID返回所有子节点ID,包含自身ID")
+    @GetMapping(value = "/child")
+    @PreAuthorize("@el.check('menu:list','roles:list')")
+    public ResponseEntity<Object> childMenu(@RequestParam Long id){
+        Set<Menu> menuSet = new HashSet<>();
+        List<Menu> menuList = menuService.getMenus(id);
+        menuSet.add(menuService.getById(id));
+        menuSet = menuService.getChildMenus(menuList, menuSet);
+        Set<Long> ids = menuSet.stream().map(Menu::getId).collect(Collectors.toSet());
+        return new ResponseEntity<>(ids,HttpStatus.OK);
+    }
+
+    @GetMapping
+    @ApiOperation("查询菜单")
+    @PreAuthorize("@el.check('menu:list')")
+    public ResponseEntity<PageResult<Menu>> queryMenu(MenuQueryCriteria criteria) throws Exception {
+        List<Menu> menuList = menuService.queryAll(criteria, true);
+        return new ResponseEntity<>(PageUtil.toPage(menuList),HttpStatus.OK);
+    }
+
+    @ApiOperation("查询菜单:根据ID获取同级与上级数据")
+    @PostMapping("/superior")
+    @PreAuthorize("@el.check('menu:list')")
+    public ResponseEntity<List<Menu>> getMenuSuperior(@RequestBody List<Long> ids) {
+        Set<Menu> menus = new LinkedHashSet<>();
+        if(CollectionUtil.isNotEmpty(ids)){
+            for (Long id : ids) {
+                Menu menu = menuService.findById(id);
+                List<Menu> menuList = menuService.getSuperior(menu, new ArrayList<>());
+                for (Menu data : menuList) {
+                    if(data.getId().equals(menu.getPid())) {
+                        data.setSubCount(data.getSubCount() - 1);
+                    }
+                }
+                menus.addAll(menuList);
+            }
+            // 编辑菜单时不显示自己以及自己下级的数据,避免出现PID数据环形问题
+            menus = menus.stream().filter(i -> !ids.contains(i.getId())).collect(Collectors.toSet());
+            return new ResponseEntity<>(menuService.buildTree(new ArrayList<>(menus)),HttpStatus.OK);
+        }
+        return new ResponseEntity<>(menuService.getMenus(null),HttpStatus.OK);
+    }
+
+    @Log("新增菜单")
+    @ApiOperation("新增菜单")
+    @PostMapping
+    @PreAuthorize("@el.check('menu:add')")
+    public ResponseEntity<Object> createMenu(@Validated @RequestBody Menu resources){
+        if (resources.getId() != null) {
+            throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID");
+        }
+        menuService.create(resources);
+        return new ResponseEntity<>(HttpStatus.CREATED);
+    }
+
+    @Log("修改菜单")
+    @ApiOperation("修改菜单")
+    @PutMapping
+    @PreAuthorize("@el.check('menu:edit')")
+    public ResponseEntity<Object> updateMenu(@Validated(Menu.Update.class) @RequestBody Menu resources){
+        menuService.update(resources);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Log("删除菜单")
+    @ApiOperation("删除菜单")
+    @DeleteMapping
+    @PreAuthorize("@el.check('menu:del')")
+    public ResponseEntity<Object> deleteMenu(@RequestBody Set<Long> ids){
+        Set<Menu> menuSet = new HashSet<>();
+        for (Long id : ids) {
+            List<Menu> menuList = menuService.getMenus(id);
+            menuSet.add(menuService.getById(id));
+            menuSet = menuService.getChildMenus(menuList, menuSet);
+        }
+        menuService.delete(menuSet);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/rest/MonitorController.java b/oying-system/src/main/java/com/oying/modules/system/rest/MonitorController.java
new file mode 100644
index 0000000..27eaf86
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/rest/MonitorController.java
@@ -0,0 +1,30 @@
+package com.oying.modules.system.rest;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import com.oying.modules.system.service.MonitorService;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author Z
+ * @date 2020-05-02
+ */
+@RestController
+@RequiredArgsConstructor
+@Api(tags = "系统-服务监控管理")
+@RequestMapping("/api/monitor")
+public class MonitorController {
+
+    private final MonitorService serverService;
+
+    @GetMapping
+    @ApiOperation("查询服务监控")
+    @PreAuthorize("@el.check('monitor:list')")
+    public ResponseEntity<Object> queryMonitor(){
+        return new ResponseEntity<>(serverService.getServers(),HttpStatus.OK);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/rest/RoleController.java b/oying-system/src/main/java/com/oying/modules/system/rest/RoleController.java
new file mode 100644
index 0000000..fd62232
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/rest/RoleController.java
@@ -0,0 +1,139 @@
+package com.oying.modules.system.rest;
+
+import cn.hutool.core.lang.Dict;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.modules.system.domain.Role;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import com.oying.annotation.Log;
+import com.oying.exception.BadRequestException;
+import com.oying.modules.system.service.RoleService;
+import com.oying.modules.system.domain.dto.RoleQueryCriteria;
+import com.oying.utils.PageResult;
+import com.oying.utils.SecurityUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author Z
+ * @date 2018-12-03
+ */
+@RestController
+@RequiredArgsConstructor
+@Api(tags = "系统:角色管理")
+@RequestMapping("/api/roles")
+public class RoleController {
+
+    private final RoleService roleService;
+
+    private static final String ENTITY_NAME = "role";
+
+    @ApiOperation("获取单个role")
+    @GetMapping(value = "/{id}")
+    @PreAuthorize("@el.check('roles:list')")
+    public ResponseEntity<Role> findRoleById(@PathVariable Long id){
+        return new ResponseEntity<>(roleService.findById(id), HttpStatus.OK);
+    }
+
+    @ApiOperation("导出角色数据")
+    @GetMapping(value = "/download")
+    @PreAuthorize("@el.check('role:list')")
+    public void exportRole(HttpServletResponse response, RoleQueryCriteria criteria) throws IOException {
+        roleService.download(roleService.queryAll(criteria), response);
+    }
+
+    @ApiOperation("返回全部的角色")
+    @GetMapping(value = "/all")
+    @PreAuthorize("@el.check('roles:list','user:add','user:edit')")
+    public ResponseEntity<List<Role>> queryAllRole(){
+        return new ResponseEntity<>(roleService.queryAll(),HttpStatus.OK);
+    }
+
+    @ApiOperation("查询角色")
+    @GetMapping
+    @PreAuthorize("@el.check('roles:list')")
+    public ResponseEntity<PageResult<Role>> queryRole(RoleQueryCriteria criteria){
+        Page<Object> page = new Page<>(criteria.getPage(), criteria.getSize());
+        return new ResponseEntity<>(roleService.queryAll(criteria, page),HttpStatus.OK);
+    }
+
+    @ApiOperation("获取用户级别")
+    @GetMapping(value = "/level")
+    public ResponseEntity<Object> getRoleLevel(){
+        return new ResponseEntity<>(Dict.create().set("level", getLevels(null)),HttpStatus.OK);
+    }
+
+    @Log("新增角色")
+    @ApiOperation("新增角色")
+    @PostMapping
+    @PreAuthorize("@el.check('roles:add')")
+    public ResponseEntity<Object> createRole(@Validated @RequestBody Role resources){
+        if (resources.getId() != null) {
+            throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID");
+        }
+        getLevels(resources.getLevel());
+        roleService.create(resources);
+        return new ResponseEntity<>(HttpStatus.CREATED);
+    }
+
+    @Log("修改角色")
+    @ApiOperation("修改角色")
+    @PutMapping
+    @PreAuthorize("@el.check('roles:edit')")
+    public ResponseEntity<Object> updateRole(@Validated(Role.Update.class) @RequestBody Role resources){
+        getLevels(resources.getLevel());
+        roleService.update(resources);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Log("修改角色菜单")
+    @ApiOperation("修改角色菜单")
+    @PutMapping(value = "/menu")
+    @PreAuthorize("@el.check('roles:edit')")
+    public ResponseEntity<Object> updateRoleMenu(@RequestBody Role resources){
+        Role role = roleService.getById(resources.getId());
+        getLevels(role.getLevel());
+        roleService.updateMenu(resources);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Log("删除角色")
+    @ApiOperation("删除角色")
+    @DeleteMapping
+    @PreAuthorize("@el.check('roles:del')")
+    public ResponseEntity<Object> deleteRole(@RequestBody Set<Long> ids){
+        for (Long id : ids) {
+            Role role = roleService.getById(id);
+            getLevels(role.getLevel());
+        }
+        // 验证是否被用户关联
+        roleService.verification(ids);
+        roleService.delete(ids);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * 获取用户的角色级别
+     * @return /
+     */
+    private int getLevels(Integer level){
+        List<Integer> levels = roleService.findByUsersId(SecurityUtils.getCurrentUserId()).stream().map(Role::getLevel).collect(Collectors.toList());
+        int min = Collections.min(levels);
+        if(level != null){
+            if(level < min){
+                throw new BadRequestException("权限不足,你的角色级别:" + min + ",低于操作的角色级别:" + level);
+            }
+        }
+        return min;
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/rest/UserController.java b/oying-system/src/main/java/com/oying/modules/system/rest/UserController.java
new file mode 100644
index 0000000..a1ce620
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/rest/UserController.java
@@ -0,0 +1,197 @@
+package com.oying.modules.system.rest;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.modules.system.domain.Dept;
+import com.oying.modules.system.domain.Role;
+import com.oying.modules.system.domain.User;
+import com.oying.modules.system.domain.dto.UserPassVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import com.oying.utils.PageResult;
+import com.oying.utils.PageUtil;
+import com.oying.utils.RsaUtils;
+import com.oying.utils.SecurityUtils;
+import com.oying.annotation.Log;
+import com.oying.config.properties.RsaProperties;
+import com.oying.modules.system.service.DataService;
+import com.oying.exception.BadRequestException;
+import com.oying.modules.system.service.DeptService;
+import com.oying.modules.system.service.RoleService;
+import com.oying.modules.system.domain.dto.UserQueryCriteria;
+import com.oying.modules.system.service.VerifyService;
+import com.oying.modules.system.service.UserService;
+import com.oying.utils.enums.CodeEnum;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author Z
+ * @date 2018-11-23
+ */
+@Api(tags = "系统:用户管理")
+@RestController
+@RequestMapping("/api/users")
+@RequiredArgsConstructor
+public class UserController {
+
+    private final PasswordEncoder passwordEncoder;
+    private final UserService userService;
+    private final DataService dataService;
+    private final DeptService deptService;
+    private final RoleService roleService;
+    private final VerifyService verificationCodeService;
+
+    @ApiOperation("导出用户数据")
+    @GetMapping(value = "/download")
+    @PreAuthorize("@el.check('user:list')")
+    public void exportUser(HttpServletResponse response, UserQueryCriteria criteria) throws IOException {
+        userService.download(userService.queryAll(criteria), response);
+    }
+
+    @ApiOperation("查询用户")
+    @GetMapping
+    @PreAuthorize("@el.check('user:list')")
+    public ResponseEntity<PageResult<User>> queryUser(UserQueryCriteria criteria){
+        Page<Object> page = new Page<>(criteria.getPage(), criteria.getSize());
+        if (!ObjectUtils.isEmpty(criteria.getDeptId())) {
+            criteria.getDeptIds().add(criteria.getDeptId());
+            // 先查找是否存在子节点
+            List<Dept> data = deptService.findByPid(criteria.getDeptId());
+            // 然后把子节点的ID都加入到集合中
+            criteria.getDeptIds().addAll(deptService.getDeptChildren(data));
+        }
+        // 数据权限
+        List<Long> dataScopes = dataService.getDeptIds(userService.findByName(SecurityUtils.getCurrentUsername()));
+        // criteria.getDeptIds() 不为空并且数据权限不为空则取交集
+        if (!CollectionUtils.isEmpty(criteria.getDeptIds()) && !CollectionUtils.isEmpty(dataScopes)){
+            // 取交集
+            criteria.getDeptIds().retainAll(dataScopes);
+            if(!CollectionUtil.isEmpty(criteria.getDeptIds())){
+                return new ResponseEntity<>(userService.queryAll(criteria,page),HttpStatus.OK);
+            }
+        } else {
+            // 否则取并集
+            criteria.getDeptIds().addAll(dataScopes);
+            return new ResponseEntity<>(userService.queryAll(criteria,page),HttpStatus.OK);
+        }
+        return new ResponseEntity<>(PageUtil.noData(),HttpStatus.OK);
+    }
+
+    @Log("新增用户")
+    @ApiOperation("新增用户")
+    @PostMapping
+    @PreAuthorize("@el.check('user:add')")
+    public ResponseEntity<Object> createUser(@Validated @RequestBody User resources){
+        checkLevel(resources);
+        // 默认密码 123456
+        resources.setPassword(passwordEncoder.encode("123456"));
+        userService.create(resources);
+        return new ResponseEntity<>(HttpStatus.CREATED);
+    }
+
+    @Log("修改用户")
+    @ApiOperation("修改用户")
+    @PutMapping
+    @PreAuthorize("@el.check('user:edit')")
+    public ResponseEntity<Object> updateUser(@Validated(User.Update.class) @RequestBody User resources) throws Exception {
+        checkLevel(resources);
+        userService.update(resources);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Log("修改用户:个人中心")
+    @ApiOperation("修改用户:个人中心")
+    @PutMapping(value = "center")
+    public ResponseEntity<Object> centerUser(@Validated(User.Update.class) @RequestBody User resources){
+        if(!resources.getId().equals(SecurityUtils.getCurrentUserId())){
+            throw new BadRequestException("不能修改他人资料");
+        }
+        userService.updateCenter(resources);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Log("删除用户")
+    @ApiOperation("删除用户")
+    @DeleteMapping
+    @PreAuthorize("@el.check('user:del')")
+    public ResponseEntity<Object> deleteUser(@RequestBody Set<Long> ids){
+        for (Long id : ids) {
+            Integer currentLevel =  Collections.min(roleService.findByUsersId(SecurityUtils.getCurrentUserId()).stream().map(Role::getLevel).collect(Collectors.toList()));
+            Integer optLevel =  Collections.min(roleService.findByUsersId(id).stream().map(Role::getLevel).collect(Collectors.toList()));
+            if (currentLevel > optLevel) {
+                throw new BadRequestException("角色权限不足,不能删除:" + userService.findById(id).getUsername());
+            }
+        }
+        userService.delete(ids);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation("修改密码")
+    @PostMapping(value = "/updatePass")
+    public ResponseEntity<Object> updateUserPass(@RequestBody UserPassVo passVo) throws Exception {
+        String oldPass = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey,passVo.getOldPass());
+        String newPass = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey,passVo.getNewPass());
+        User user = userService.findByName(SecurityUtils.getCurrentUsername());
+        if(!passwordEncoder.matches(oldPass, user.getPassword())){
+            throw new BadRequestException("修改失败,旧密码错误");
+        }
+        if(passwordEncoder.matches(newPass, user.getPassword())){
+            throw new BadRequestException("新密码不能与旧密码相同");
+        }
+        userService.updatePass(user.getUsername(),passwordEncoder.encode(newPass));
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation("重置密码")
+    @PutMapping(value = "/resetPwd")
+    public ResponseEntity<Object> resetPwd(@RequestBody Set<Long> ids) {
+        String pwd = passwordEncoder.encode("123456");
+        userService.resetPwd(ids, pwd);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation("修改头像")
+    @PostMapping(value = "/updateAvatar")
+    public ResponseEntity<Object> updateUserAvatar(@RequestParam MultipartFile avatar){
+        return new ResponseEntity<>(userService.updateAvatar(avatar), HttpStatus.OK);
+    }
+
+    @Log("修改邮箱")
+    @ApiOperation("修改邮箱")
+    @PostMapping(value = "/updateEmail/{code}")
+    public ResponseEntity<Object> updateUserEmail(@PathVariable String code, @RequestBody User resources) throws Exception {
+        String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey,resources.getPassword());
+        User user = userService.findByName(SecurityUtils.getCurrentUsername());
+        if(!passwordEncoder.matches(password, user.getPassword())){
+            throw new BadRequestException("密码错误");
+        }
+        verificationCodeService.validated(CodeEnum.EMAIL_RESET_EMAIL_CODE.getKey() + resources.getEmail(), code);
+        userService.updateEmail(user.getUsername(),resources.getEmail());
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * 如果当前用户的角色级别低于创建用户的角色级别,则抛出权限不足的错误
+     * @param resources /
+     */
+    private void checkLevel(User resources) {
+        Integer currentLevel =  Collections.min(roleService.findByUsersId(SecurityUtils.getCurrentUserId()).stream().map(Role::getLevel).collect(Collectors.toList()));
+        Integer optLevel = roleService.findByRoles(resources.getRoles());
+        if (currentLevel > optLevel) {
+            throw new BadRequestException("角色权限不足");
+        }
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/rest/VerifyController.java b/oying-system/src/main/java/com/oying/modules/system/rest/VerifyController.java
new file mode 100644
index 0000000..ca1b4e9
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/rest/VerifyController.java
@@ -0,0 +1,61 @@
+package com.oying.modules.system.rest;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import com.oying.domain.dto.EmailDto;
+import com.oying.service.EmailService;
+import com.oying.modules.system.service.VerifyService;
+import com.oying.utils.enums.CodeBiEnum;
+import com.oying.utils.enums.CodeEnum;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import java.util.Objects;
+
+/**
+ * @author Z
+ * @date 2018-12-26
+ */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/code")
+@Api(tags = "系统:验证码管理")
+public class VerifyController {
+
+    private final VerifyService verificationCodeService;
+    private final EmailService emailService;
+
+    @PostMapping(value = "/resetEmail")
+    @ApiOperation("重置邮箱,发送验证码")
+    public ResponseEntity<Object> resetEmail(@RequestParam String email){
+        EmailDto emailDto = verificationCodeService.sendEmail(email, CodeEnum.EMAIL_RESET_EMAIL_CODE.getKey());
+        emailService.send(emailDto,emailService.find());
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @PostMapping(value = "/email/resetPass")
+    @ApiOperation("重置密码,发送验证码")
+    public ResponseEntity<Object> resetPass(@RequestParam String email){
+        EmailDto emailDto = verificationCodeService.sendEmail(email, CodeEnum.EMAIL_RESET_PWD_CODE.getKey());
+        emailService.send(emailDto,emailService.find());
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @GetMapping(value = "/validated")
+    @ApiOperation("验证码验证")
+    public ResponseEntity<Object> validated(@RequestParam String email, @RequestParam String code, @RequestParam Integer codeBi){
+        CodeBiEnum biEnum = CodeBiEnum.find(codeBi);
+        switch (Objects.requireNonNull(biEnum)){
+            case ONE:
+                verificationCodeService.validated(CodeEnum.EMAIL_RESET_EMAIL_CODE.getKey() + email ,code);
+                break;
+            case TWO:
+                verificationCodeService.validated(CodeEnum.EMAIL_RESET_PWD_CODE.getKey() + email ,code);
+                break;
+            default:
+                break;
+        }
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/DataService.java b/oying-system/src/main/java/com/oying/modules/system/service/DataService.java
new file mode 100644
index 0000000..bd538bd
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/DataService.java
@@ -0,0 +1,20 @@
+package com.oying.modules.system.service;
+
+import com.oying.modules.system.domain.User;
+
+import java.util.List;
+
+/**
+ * 数据权限服务类
+ * @author Z
+ * @date 2020-05-07
+ */
+public interface DataService {
+
+    /**
+     * 获取数据权限
+     * @param user /
+     * @return /
+     */
+    List<Long> getDeptIds(User user);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/DeptService.java b/oying-system/src/main/java/com/oying/modules/system/service/DeptService.java
new file mode 100644
index 0000000..10c8c25
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/DeptService.java
@@ -0,0 +1,110 @@
+package com.oying.modules.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.modules.system.domain.Dept;
+import com.oying.modules.system.domain.dto.DeptQueryCriteria;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/**
+* @author Z
+* @date 2019-03-25
+*/
+public interface DeptService extends IService<Dept> {
+
+    /**
+     * 查询所有数据
+     * @param criteria 条件
+     * @param isQuery /
+     * @throws Exception /
+     * @return /
+     */
+    List<Dept> queryAll(DeptQueryCriteria criteria, Boolean isQuery) throws Exception;
+
+    /**
+     * 根据ID查询
+     * @param id /
+     * @return /
+     */
+    Dept findById(Long id);
+
+    /**
+     * 创建
+     * @param resources /
+     */
+    void create(Dept resources);
+
+    /**
+     * 编辑
+     * @param resources /
+     */
+    void update(Dept resources);
+
+    /**
+     * 删除
+     * @param depts /
+     *
+     */
+    void delete(Set<Dept> depts);
+
+    /**
+     * 根据PID查询
+     * @param pid /
+     * @return /
+     */
+    List<Dept> findByPid(long pid);
+
+    /**
+     * 根据角色ID查询
+     * @param id /
+     * @return /
+     */
+    Set<Dept> findByRoleId(Long id);
+
+    /**
+     * 导出数据
+     * @param depts 待导出的数据
+     * @param response /
+     * @throws IOException /
+     */
+    void download(List<Dept> depts, HttpServletResponse response) throws IOException;
+
+    /**
+     * 获取待删除的机构
+     * @param deptList /
+     * @param depts /
+     * @return /
+     */
+    Set<Dept> getDeleteDepts(List<Dept> deptList, Set<Dept> depts);
+
+    /**
+     * 根据ID获取同级与上级数据
+     * @param dept /
+     * @param depts /
+     * @return /
+     */
+    List<Dept> getSuperior(Dept dept, List<Dept> depts);
+
+    /**
+     * 构建树形数据
+     * @param depts /
+     * @return /
+     */
+    Object buildTree(List<Dept> depts);
+
+    /**
+     * 获取
+     * @param deptList 、
+     * @return 、
+     */
+    List<Long> getDeptChildren(List<Dept> deptList);
+
+    /**
+     * 验证是否被角色或用户关联
+     * @param depts /
+     */
+    void verification(Set<Dept> depts);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/DictDetailService.java b/oying-system/src/main/java/com/oying/modules/system/service/DictDetailService.java
new file mode 100644
index 0000000..69dd9e3
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/DictDetailService.java
@@ -0,0 +1,50 @@
+package com.oying.modules.system.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.modules.system.domain.DictDetail;
+import com.oying.modules.system.domain.dto.DictDetailQueryCriteria;
+import com.oying.utils.PageResult;
+
+import java.util.List;
+
+/**
+* @author Z
+* @date 2019-04-10
+*/
+public interface DictDetailService extends IService<DictDetail> {
+
+    /**
+     * 创建
+     * @param resources /
+     */
+    void create(DictDetail resources);
+
+    /**
+     * 编辑
+     * @param resources /
+     */
+    void update(DictDetail resources);
+
+    /**
+     * 删除
+     * @param id /
+     */
+    void delete(Long id);
+
+    /**
+     * 分页查询
+     *
+     * @param criteria 条件
+     * @param page     分页参数
+     * @return /
+     */
+    PageResult<DictDetail> queryAll(DictDetailQueryCriteria criteria, Page<Object> page);
+
+    /**
+     * 根据字典名称获取字典详情
+     * @param name 字典名称
+     * @return /
+     */
+    List<DictDetail> getDictByName(String name);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/DictService.java b/oying-system/src/main/java/com/oying/modules/system/service/DictService.java
new file mode 100644
index 0000000..c04f35e
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/DictService.java
@@ -0,0 +1,61 @@
+package com.oying.modules.system.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.modules.system.domain.Dict;
+import com.oying.modules.system.domain.dto.DictQueryCriteria;
+import com.oying.utils.PageResult;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/**
+* @author Z
+* @date 2019-04-10
+*/
+public interface DictService extends IService<Dict> {
+
+    /**
+     * 分页查询
+     *
+     * @param criteria 条件
+     * @param page     分页参数
+     * @return /
+     */
+    PageResult<Dict> queryAll(DictQueryCriteria criteria, Page<Object> page);
+
+    /**
+     * 查询全部数据
+     * @param criteria /
+     * @return /
+     */
+    List<Dict> queryAll(DictQueryCriteria criteria);
+
+    /**
+     * 创建
+     * @param resources /
+     */
+    void create(Dict resources);
+
+    /**
+     * 编辑
+     * @param resources /
+     */
+    void update(Dict resources);
+
+    /**
+     * 删除
+     * @param ids /
+     */
+    void delete(Set<Long> ids);
+
+    /**
+     * 导出数据
+     * @param queryAll 待导出的数据
+     * @param response /
+     * @throws IOException /
+     */
+    void download(List<Dict> queryAll, HttpServletResponse response) throws IOException;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/JobService.java b/oying-system/src/main/java/com/oying/modules/system/service/JobService.java
new file mode 100644
index 0000000..bd7d8cb
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/JobService.java
@@ -0,0 +1,74 @@
+package com.oying.modules.system.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.modules.system.domain.Job;
+import com.oying.modules.system.domain.dto.JobQueryCriteria;
+import com.oying.utils.PageResult;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/**
+* @author Z
+* @date 2019-03-29
+*/
+public interface JobService extends IService<Job> {
+
+    /**
+     * 根据ID查询
+     * @param id /
+     * @return /
+     */
+    Job findById(Long id);
+
+    /**
+     * 创建
+     * @param resources /
+     */
+    void create(Job resources);
+
+    /**
+     * 编辑
+     * @param resources /
+     */
+    void update(Job resources);
+
+    /**
+     * 删除
+     * @param ids /
+     */
+    void delete(Set<Long> ids);
+
+    /**
+     * 分页查询
+     *
+     * @param criteria 条件
+     * @param page     分页参数
+     * @return /
+     */
+    PageResult<Job> queryAll(JobQueryCriteria criteria, Page<Object> page);
+
+    /**
+     * 查询全部数据
+     * @param criteria /
+     * @return /
+     */
+    List<Job> queryAll(JobQueryCriteria criteria);
+
+    /**
+     * 导出数据
+     * @param jobs 待导出的数据
+     * @param response /
+     * @throws IOException /
+     */
+    void download(List<Job> jobs, HttpServletResponse response) throws IOException;
+
+    /**
+     * 验证是否被用户关联
+     * @param ids /
+     */
+    void verification(Set<Long> ids);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/MenuService.java b/oying-system/src/main/java/com/oying/modules/system/service/MenuService.java
new file mode 100644
index 0000000..95c511d
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/MenuService.java
@@ -0,0 +1,104 @@
+package com.oying.modules.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.modules.system.domain.Menu;
+import com.oying.modules.system.domain.dto.MenuQueryCriteria;
+import com.oying.modules.system.domain.dto.MenuVo;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2018-12-17
+ */
+public interface MenuService extends IService<Menu> {
+
+    /**
+     * 查询全部数据
+     * @param criteria 条件
+     * @param isQuery /
+     * @throws Exception /
+     * @return /
+     */
+    List<Menu> queryAll(MenuQueryCriteria criteria, Boolean isQuery) throws Exception;
+
+    /**
+     * 根据ID查询
+     * @param id /
+     * @return /
+     */
+    Menu findById(long id);
+
+    /**
+     * 创建
+     * @param resources /
+     */
+    void create(Menu resources);
+
+    /**
+     * 编辑
+     * @param resources /
+     */
+    void update(Menu resources);
+
+    /**
+     * 获取所有子节点,包含自身ID
+     * @param menuList /
+     * @param menuSet /
+     * @return /
+     */
+    Set<Menu> getChildMenus(List<Menu> menuList, Set<Menu> menuSet);
+
+    /**
+     * 构建菜单树
+     * @param menus 原始数据
+     * @return /
+     */
+    List<Menu> buildTree(List<Menu> menus);
+
+    /**
+     * 构建菜单树
+     * @param menus /
+     * @return /
+     */
+    List<MenuVo> buildMenus(List<Menu> menus);
+
+    /**
+     * 删除
+     * @param menuSet /
+     */
+    void delete(Set<Menu> menuSet);
+
+    /**
+     * 导出
+     * @param menus 待导出的数据
+     * @param response /
+     * @throws IOException /
+     */
+    void download(List<Menu> menus, HttpServletResponse response) throws IOException;
+
+    /**
+     * 懒加载菜单数据
+     * @param pid /
+     * @return /
+     */
+    List<Menu> getMenus(Long pid);
+
+    /**
+     * 根据ID获取同级与上级数据
+     * @param menu /
+     * @param objects /
+     * @return /
+     */
+    List<Menu> getSuperior(Menu menu, List<Menu> objects);
+
+    /**
+     * 根据当前用户获取菜单
+     * @param currentUserId /
+     * @return /
+     */
+    List<Menu> findByUser(Long currentUserId);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/MonitorService.java b/oying-system/src/main/java/com/oying/modules/system/service/MonitorService.java
new file mode 100644
index 0000000..2668c75
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/MonitorService.java
@@ -0,0 +1,16 @@
+package com.oying.modules.system.service;
+
+import java.util.Map;
+
+/**
+ * @author Z
+ * @date 2020-05-02
+ */
+public interface MonitorService {
+
+    /**
+    * 查询数据分页
+    * @return Map<String,Object>
+    */
+    Map<String,Object> getServers();
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/RoleService.java b/oying-system/src/main/java/com/oying/modules/system/service/RoleService.java
new file mode 100644
index 0000000..1912ec9
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/RoleService.java
@@ -0,0 +1,116 @@
+package com.oying.modules.system.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.modules.security.service.dto.AuthorityDto;
+import com.oying.modules.system.domain.Role;
+import com.oying.modules.system.domain.User;
+import com.oying.modules.system.domain.dto.RoleQueryCriteria;
+import com.oying.utils.PageResult;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2018-12-03
+ */
+public interface RoleService extends IService<Role> {
+
+    /**
+     * 查询全部数据
+     * @return /
+     */
+    List<Role> queryAll();
+
+    /**
+     * 根据ID查询
+     * @param id /
+     * @return /
+     */
+    Role findById(long id);
+
+    /**
+     * 创建
+     * @param resources /
+     */
+    void create(Role resources);
+
+    /**
+     * 编辑
+     * @param resources /
+     */
+    void update(Role resources);
+
+    /**
+     * 删除
+     * @param ids /
+     */
+    void delete(Set<Long> ids);
+
+    /**
+     * 根据用户ID查询
+     * @param userId 用户ID
+     * @return /
+     */
+    List<Role> findByUsersId(Long userId);
+
+    /**
+     * 根据角色查询角色级别
+     * @param roles /
+     * @return /
+     */
+    Integer findByRoles(Set<Role> roles);
+
+    /**
+     * 修改绑定的菜单
+     * @param resources /
+     */
+    void updateMenu(Role resources);
+
+    /**
+     * 待条件分页查询
+     *
+     * @param criteria 条件
+     * @param page     分页参数
+     * @return /
+     */
+    PageResult<Role> queryAll(RoleQueryCriteria criteria, Page<Object> page);
+
+    /**
+     * 查询全部
+     * @param criteria 条件
+     * @return /
+     */
+    List<Role> queryAll(RoleQueryCriteria criteria);
+
+    /**
+     * 导出数据
+     * @param roles 待导出的数据
+     * @param response /
+     * @throws IOException /
+     */
+    void download(List<Role> roles, HttpServletResponse response) throws IOException;
+
+    /**
+     * 获取用户权限信息
+     * @param user 用户信息
+     * @return 权限信息
+     */
+    List<AuthorityDto> buildPermissions(User user);
+
+    /**
+     * 验证是否被用户关联
+     * @param ids /
+     */
+    void verification(Set<Long> ids);
+
+    /**
+     * 根据菜单Id查询
+     * @param menuId /
+     * @return /
+     */
+    List<Role> findByMenuId(Long menuId);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/UserService.java b/oying-system/src/main/java/com/oying/modules/system/service/UserService.java
new file mode 100644
index 0000000..cfdeb44
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/UserService.java
@@ -0,0 +1,121 @@
+package com.oying.modules.system.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.modules.system.domain.User;
+import com.oying.modules.system.domain.dto.UserQueryCriteria;
+import com.oying.utils.PageResult;
+import org.springframework.web.multipart.MultipartFile;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Z
+ * @date 2018-11-23
+ */
+public interface UserService extends IService<User> {
+
+    /**
+     * 根据ID查询
+     *
+     * @param id ID
+     * @return /
+     */
+    User findById(long id);
+
+    /**
+     * 新增用户
+     * @param resources /
+     */
+    void create(User resources);
+
+    /**
+     * 编辑用户
+     * @param resources /
+     * @throws Exception /
+     */
+    void update(User resources) throws Exception;
+
+    /**
+     * 删除用户
+     * @param ids /
+     */
+    void delete(Set<Long> ids);
+
+    /**
+     * 根据用户名查询
+     *
+     * @param userName /
+     * @return /
+     */
+    User findByName(String userName);
+
+    /**
+     * 根据用户名查询
+     * @param userName /
+     * @return /
+     */
+    User getLoginData(String userName);
+
+    /**
+     * 修改密码
+     * @param username 用户名
+     * @param encryptPassword 密码
+     */
+    void updatePass(String username, String encryptPassword);
+
+    /**
+     * 修改头像
+     * @param file 文件
+     * @return /
+     */
+    Map<String, String> updateAvatar(MultipartFile file);
+
+    /**
+     * 修改邮箱
+     * @param username 用户名
+     * @param email 邮箱
+     */
+    void updateEmail(String username, String email);
+
+    /**
+     * 查询全部
+     *
+     * @param criteria 条件
+     * @param page     分页参数
+     * @return /
+     */
+    PageResult<User> queryAll(UserQueryCriteria criteria, Page<Object> page);
+
+    /**
+     * 查询全部不分页
+     *
+     * @param criteria 条件
+     * @return /
+     */
+    List<User> queryAll(UserQueryCriteria criteria);
+
+    /**
+     * 导出数据
+     * @param queryAll 待导出的数据
+     * @param response /
+     * @throws IOException /
+     */
+    void download(List<User> queryAll, HttpServletResponse response) throws IOException;
+
+    /**
+     * 用户自助修改资料
+     * @param resources /
+     */
+    void updateCenter(User resources);
+
+    /**
+     * 重置密码
+     * @param ids 用户id
+     * @param pwd 密码
+     */
+    void resetPwd(Set<Long> ids, String pwd);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/VerifyService.java b/oying-system/src/main/java/com/oying/modules/system/service/VerifyService.java
new file mode 100644
index 0000000..693f2e9
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/VerifyService.java
@@ -0,0 +1,26 @@
+package com.oying.modules.system.service;
+
+import com.oying.domain.dto.EmailDto;
+
+/**
+ * @author Z
+ * @date 2018-12-26
+ */
+public interface VerifyService {
+
+    /**
+     * 发送验证码
+     * @param email /
+     * @param key /
+     * @return /
+     */
+    EmailDto sendEmail(String email, String key);
+
+
+    /**
+     * 验证
+     * @param code /
+     * @param key /
+     */
+    void validated(String key, String code);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/impl/DataServiceImpl.java b/oying-system/src/main/java/com/oying/modules/system/service/impl/DataServiceImpl.java
new file mode 100644
index 0000000..7242f12
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/impl/DataServiceImpl.java
@@ -0,0 +1,81 @@
+package com.oying.modules.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.oying.modules.system.domain.Dept;
+import com.oying.modules.system.domain.Role;
+import com.oying.modules.system.domain.User;
+import lombok.RequiredArgsConstructor;
+import com.oying.modules.system.service.DataService;
+import com.oying.modules.system.service.DeptService;
+import com.oying.modules.system.service.RoleService;
+import com.oying.utils.CacheKey;
+import com.oying.utils.RedisUtils;
+import com.oying.utils.enums.DataScopeEnum;
+import org.springframework.stereotype.Service;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Z
+ * @description 数据权限服务实现
+ * @date 2020-05-07
+ **/
+@Service
+@RequiredArgsConstructor
+public class DataServiceImpl implements DataService {
+
+    private final RedisUtils redisUtils;
+    private final RoleService roleService;
+    private final DeptService deptService;
+
+    /**
+     * 用户角色和用户机构改变时需清理缓存
+     * @param user /
+     * @return /
+     */
+    @Override
+    public List<Long> getDeptIds(User user) {
+        String key = CacheKey.DATA_USER + user.getId();
+        List<Long> ids = redisUtils.getList(key, Long.class);
+        if (CollUtil.isEmpty(ids)) {
+            Set<Long> deptIds = new HashSet<>();
+            // 查询用户角色
+            List<Role> roleList = roleService.findByUsersId(user.getId());
+            // 获取对应的机构ID
+            for (Role role : roleList) {
+                DataScopeEnum dataScopeEnum = DataScopeEnum.find(role.getDataScope());
+                switch (Objects.requireNonNull(dataScopeEnum)) {
+                    case THIS_LEVEL:
+                        deptIds.add(user.getDept().getId());
+                        break;
+                    case CUSTOMIZE:
+                        deptIds.addAll(getCustomize(deptIds, role));
+                        break;
+                    default:
+                        return new ArrayList<>();
+                }
+            }
+            ids = new ArrayList<>(deptIds);
+            redisUtils.set(key, ids, 1, TimeUnit.DAYS);
+        }
+        return ids;
+    }
+
+    /**
+     * 获取自定义的数据权限
+     * @param deptIds 机构ID
+     * @param role 角色
+     * @return 数据权限ID
+     */
+    public Set<Long> getCustomize(Set<Long> deptIds, Role role){
+        Set<Dept> depts = deptService.findByRoleId(role.getId());
+        for (Dept dept : depts) {
+            deptIds.add(dept.getId());
+            List<Dept> deptChildren = deptService.findByPid(dept.getId());
+            if (CollUtil.isNotEmpty(deptChildren)) {
+                deptIds.addAll(deptService.getDeptChildren(deptChildren));
+            }
+        }
+        return deptIds;
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/impl/DeptServiceImpl.java b/oying-system/src/main/java/com/oying/modules/system/service/impl/DeptServiceImpl.java
new file mode 100644
index 0000000..0c3386b
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/impl/DeptServiceImpl.java
@@ -0,0 +1,265 @@
+package com.oying.modules.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.oying.modules.system.domain.Dept;
+import com.oying.modules.system.domain.User;
+import com.oying.modules.system.domain.dto.DeptQueryCriteria;
+import com.oying.modules.system.mapper.DeptMapper;
+import com.oying.modules.system.mapper.RoleMapper;
+import com.oying.modules.system.mapper.UserMapper;
+import com.oying.modules.system.service.DeptService;
+import com.oying.utils.*;
+import lombok.RequiredArgsConstructor;
+import com.oying.exception.BadRequestException;
+import com.oying.utils.enums.DataScopeEnum;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+* @author Z
+* @date 2019-03-25
+*/
+@Service
+@RequiredArgsConstructor
+public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements DeptService {
+
+    private final DeptMapper deptMapper;
+    private final UserMapper userMapper;
+    private final RedisUtils redisUtils;
+    private final RoleMapper roleMapper;
+
+    @Override
+    public List<Dept> queryAll(DeptQueryCriteria criteria, Boolean isQuery) throws Exception {
+        String dataScopeType = SecurityUtils.getDataScopeType();
+        if (isQuery) {
+            if(dataScopeType.equals(DataScopeEnum.ALL.getValue())){
+                criteria.setPidIsNull(true);
+            }
+            List<Field> fields = StringUtils.getAllFields(criteria.getClass(), new ArrayList<>());
+            List<String> fieldNames = new ArrayList<String>(){{ add("pidIsNull");add("enabled");}};
+            for (Field field : fields) {
+                //设置对象的访问权限,保证对private的属性的访问
+                field.setAccessible(true);
+                Object val = field.get(criteria);
+                if(fieldNames.contains(field.getName())){
+                    continue;
+                }
+                if (ObjectUtil.isNotNull(val)) {
+                    criteria.setPidIsNull(null);
+                    break;
+                }
+            }
+        }
+        // 数据权限
+        criteria.setIds(SecurityUtils.getCurrentUserDataScope());
+        List<Dept> list = deptMapper.findAll(criteria);
+        // 如果为空,就代表为自定义权限或者本级权限,就需要去重,不理解可以注释掉,看查询结果
+        if(StringUtils.isBlank(dataScopeType)){
+            return deduplication(list);
+        }
+        return list;
+    }
+
+    @Override
+    public Dept findById(Long id) {
+        String key = CacheKey.DEPT_ID + id;
+        Dept dept = redisUtils.get(key, Dept.class);
+        if(dept == null){
+            dept = deptMapper.selectById(id);
+            redisUtils.set(key, dept, 1, TimeUnit.DAYS);
+        }
+        return dept;
+    }
+
+    @Override
+    public List<Dept> findByPid(long pid) {
+        return deptMapper.findByPid(pid);
+    }
+
+    @Override
+    public Set<Dept> findByRoleId(Long id) {
+        return deptMapper.findByRoleId(id);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void create(Dept resources) {
+        save(resources);
+        // 清理缓存
+        updateSubCnt(resources.getPid());
+        // 清理自定义角色权限的datascope缓存
+        delCaches(resources.getPid());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void update(Dept resources) {
+        // 旧的机构
+        Long oldPid = findById(resources.getId()).getPid();
+        Long newPid = resources.getPid();
+        if(resources.getPid() != null && resources.getId().equals(resources.getPid())) {
+            throw new BadRequestException("上级不能为自己");
+        }
+        Dept dept = getById(resources.getId());
+        resources.setId(dept.getId());
+        saveOrUpdate(resources);
+        // 更新父节点中子节点数目
+        updateSubCnt(oldPid);
+        updateSubCnt(newPid);
+        // 清理缓存
+        delCaches(resources.getId());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delete(Set<Dept> depts) {
+        for (Dept dept : depts) {
+            // 清理缓存
+            delCaches(dept.getId());
+            deptMapper.deleteById(dept.getId());
+            updateSubCnt(dept.getPid());
+        }
+    }
+
+    @Override
+    public void download(List<Dept> depts, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (Dept dept : depts) {
+            Map<String,Object> map = new LinkedHashMap<>();
+            map.put("机构名称", dept.getName());
+            map.put("机构状态", dept.getEnabled() ? "启用" : "停用");
+            map.put("创建日期", dept.getCreateTime());
+            list.add(map);
+        }
+        FileUtil.downloadExcel(list, response);
+    }
+
+    @Override
+    public Set<Dept> getDeleteDepts(List<Dept> menuList, Set<Dept> deptSet) {
+        for (Dept dept : menuList) {
+            deptSet.add(dept);
+            List<Dept> depts = deptMapper.findByPid(dept.getId());
+            if(CollUtil.isNotEmpty(depts)){
+                getDeleteDepts(depts, deptSet);
+            }
+        }
+        return deptSet;
+    }
+
+    @Override
+    public List<Long> getDeptChildren(List<Dept> deptList) {
+        List<Long> list = new ArrayList<>();
+        deptList.forEach(dept -> {
+                    if (dept!=null && dept.getEnabled()) {
+                        List<Dept> depts = deptMapper.findByPid(dept.getId());
+                        if (CollUtil.isNotEmpty(depts)) {
+                            list.addAll(getDeptChildren(depts));
+                        }
+                        list.add(dept.getId());
+                    }
+                }
+        );
+        return list;
+    }
+
+    @Override
+    public List<Dept> getSuperior(Dept dept, List<Dept> depts) {
+        if(dept.getPid() == null){
+            depts.addAll(deptMapper.findByPidIsNull());
+            return depts;
+        }
+        depts.addAll(deptMapper.findByPid(dept.getPid()));
+        return getSuperior(findById(dept.getPid()), depts);
+    }
+
+    @Override
+    public Object buildTree(List<Dept> deptList) {
+        Set<Dept> trees = new LinkedHashSet<>();
+        Set<Dept> depts= new LinkedHashSet<>();
+        List<String> deptNames = deptList.stream().map(Dept::getName).collect(Collectors.toList());
+        boolean isChild;
+        for (Dept dept : deptList) {
+            isChild = false;
+            if (dept.getPid() == null) {
+                trees.add(dept);
+            }
+            for (Dept it : deptList) {
+                if (it.getPid() != null && dept.getId().equals(it.getPid())) {
+                    isChild = true;
+                    if (dept.getChildren() == null) {
+                        dept.setChildren(new ArrayList<>());
+                    }
+                    dept.getChildren().add(it);
+                }
+            }
+            if(isChild) {
+                depts.add(dept);
+            } else if(dept.getPid() != null &&  !deptNames.contains(findById(dept.getPid()).getName())) {
+                depts.add(dept);
+            }
+        }
+
+        if (CollectionUtil.isEmpty(trees)) {
+            trees = depts;
+        }
+        Map<String,Object> map = new HashMap<>(2);
+        map.put("totalElements",depts.size());
+        map.put("content",CollectionUtil.isEmpty(trees)? depts :trees);
+        return map;
+    }
+
+    @Override
+    public void verification(Set<Dept> depts) {
+        Set<Long> deptIds = depts.stream().map(Dept::getId).collect(Collectors.toSet());
+        if(userMapper.countByDepts(deptIds) > 0){
+            throw new BadRequestException("所选机构存在用户关联,请解除后再试!");
+        }
+        if(roleMapper.countByDepts(deptIds) > 0){
+            throw new BadRequestException("所选机构存在角色关联,请解除后再试!");
+        }
+    }
+
+    private void updateSubCnt(Long deptId){
+        if(deptId != null){
+            int count = deptMapper.countByPid(deptId);
+            deptMapper.updateSubCntById(count, deptId);
+        }
+    }
+
+    private List<Dept> deduplication(List<Dept> list) {
+        List<Dept> depts = new ArrayList<>();
+        for (Dept dept : list) {
+            boolean flag = true;
+            for (Dept dept1 : list) {
+                if (dept1.getId().equals(dept.getPid())) {
+                    flag = false;
+                    break;
+                }
+            }
+            if (flag){
+                depts.add(dept);
+            }
+        }
+        return depts;
+    }
+
+    /**
+     * 清理缓存
+     * @param id /
+     */
+    public void delCaches(Long id){
+        List<User> users = userMapper.findByRoleDeptId(id);
+        // 删除数据权限
+        redisUtils.delByKeys(CacheKey.DATA_USER, users.stream().map(User::getId).collect(Collectors.toSet()));
+        redisUtils.del(CacheKey.DEPT_ID + id);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/impl/DictDetailServiceImpl.java b/oying-system/src/main/java/com/oying/modules/system/service/impl/DictDetailServiceImpl.java
new file mode 100644
index 0000000..4588eef
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/impl/DictDetailServiceImpl.java
@@ -0,0 +1,83 @@
+package com.oying.modules.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.oying.modules.system.domain.Dict;
+import com.oying.modules.system.domain.DictDetail;
+import com.oying.modules.system.domain.dto.DictDetailQueryCriteria;
+import com.oying.modules.system.mapper.DictDetailMapper;
+import com.oying.modules.system.mapper.DictMapper;
+import lombok.RequiredArgsConstructor;
+import com.oying.utils.CacheKey;
+import com.oying.utils.PageResult;
+import com.oying.utils.PageUtil;
+import com.oying.utils.RedisUtils;
+import com.oying.modules.system.service.DictDetailService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+* @author Z
+* @date 2019-04-10
+*/
+@Service
+@RequiredArgsConstructor
+public class DictDetailServiceImpl extends ServiceImpl<DictDetailMapper, DictDetail> implements DictDetailService {
+
+    private final DictMapper dictMapper;
+    private final DictDetailMapper dictDetailMapper;
+    private final RedisUtils redisUtils;
+
+    @Override
+    public PageResult<DictDetail> queryAll(DictDetailQueryCriteria criteria, Page<Object> page) {
+        return PageUtil.toPage(dictDetailMapper.findAll(criteria, page));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void create(DictDetail resources) {
+        resources.setDictId(resources.getDict().getId());
+        save(resources);
+        // 清理缓存
+        delCaches(resources);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void update(DictDetail resources) {
+        DictDetail dictDetail = getById(resources.getId());
+        resources.setId(dictDetail.getId());
+        // 更新数据
+        saveOrUpdate(resources);
+        // 清理缓存
+        delCaches(dictDetail);
+    }
+
+    @Override
+    public List<DictDetail> getDictByName(String name) {
+        String key = CacheKey.DICT_NAME + name;
+        List<DictDetail> dictDetails = redisUtils.getList(key, DictDetail.class);
+        if(CollUtil.isEmpty(dictDetails)){
+            dictDetails = dictDetailMapper.findByDictName(name);
+            redisUtils.set(key, dictDetails, 1 , TimeUnit.DAYS);
+        }
+        return dictDetails;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delete(Long id) {
+        DictDetail dictDetail = getById(id);
+        removeById(id);
+        // 清理缓存
+        delCaches(dictDetail);
+    }
+
+    public void delCaches(DictDetail dictDetail){
+        Dict dict = dictMapper.selectById(dictDetail.getDictId());
+        redisUtils.del(CacheKey.DICT_NAME + dict.getName());
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/impl/DictServiceImpl.java b/oying-system/src/main/java/com/oying/modules/system/service/impl/DictServiceImpl.java
new file mode 100644
index 0000000..d865d5d
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/impl/DictServiceImpl.java
@@ -0,0 +1,107 @@
+package com.oying.modules.system.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.oying.modules.system.domain.Dict;
+import com.oying.modules.system.domain.DictDetail;
+import com.oying.modules.system.domain.dto.DictQueryCriteria;
+import com.oying.modules.system.mapper.DictDetailMapper;
+import com.oying.modules.system.mapper.DictMapper;
+import com.oying.utils.*;
+import lombok.RequiredArgsConstructor;
+import com.oying.modules.system.service.DictService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+
+/**
+* @author Z
+* @date 2019-04-10
+*/
+@Service
+@RequiredArgsConstructor
+public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements DictService {
+
+    private final DictMapper dictMapper;
+    private final RedisUtils redisUtils;
+    private final DictDetailMapper deleteDetail;
+    private final DictDetailMapper dictDetailMapper;
+
+    @Override
+    public PageResult<Dict> queryAll(DictQueryCriteria criteria, Page<Object> page){
+        IPage<Dict> dicts = dictMapper.findAll(criteria, page);
+        return PageUtil.toPage(dicts);
+    }
+
+    @Override
+    public List<Dict> queryAll(DictQueryCriteria criteria) {
+        return dictMapper.findAll(criteria);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void create(Dict resources) {
+        save(resources);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void update(Dict resources) {
+        // 清理缓存
+        delCaches(resources);
+        Dict dict = getById(resources.getId());
+        dict.setName(resources.getName());
+        dict.setDescription(resources.getDescription());
+        saveOrUpdate(dict);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delete(Set<Long> ids) {
+        // 清理缓存
+        List<Dict> dicts = dictMapper.selectBatchIds(ids);
+        for (Dict dict : dicts) {
+            delCaches(dict);
+        }
+        // 删除字典
+        dictMapper.deleteBatchIds(ids);
+        // 删除字典详情
+        deleteDetail.deleteByDictBatchIds(ids);
+    }
+
+    @Override
+    public void download(List<Dict> dicts, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (Dict dict : dicts) {
+            List<DictDetail> dictDetails = dictDetailMapper.findByDictName(dict.getName());
+            if(CollectionUtil.isNotEmpty(dictDetails)){
+                for (DictDetail dictDetail : dictDetails) {
+                    Map<String,Object> map = new LinkedHashMap<>();
+                    map.put("字典名称", dict.getName());
+                    map.put("字典描述", dict.getDescription());
+                    map.put("字典标签", dictDetail.getLabel());
+                    map.put("字典值", dictDetail.getValue());
+                    map.put("创建日期", dictDetail.getCreateTime());
+                    list.add(map);
+                }
+            } else {
+                Map<String,Object> map = new LinkedHashMap<>();
+                map.put("字典名称", dict.getName());
+                map.put("字典描述", dict.getDescription());
+                map.put("字典标签", null);
+                map.put("字典值", null);
+                map.put("创建日期", dict.getCreateTime());
+                list.add(map);
+            }
+        }
+        FileUtil.downloadExcel(list, response);
+    }
+
+    public void delCaches(Dict dict){
+        redisUtils.del(CacheKey.DICT_NAME + dict.getName());
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/impl/JobServiceImpl.java b/oying-system/src/main/java/com/oying/modules/system/service/impl/JobServiceImpl.java
new file mode 100644
index 0000000..cfa335f
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/impl/JobServiceImpl.java
@@ -0,0 +1,109 @@
+package com.oying.modules.system.service.impl;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.oying.modules.system.domain.Job;
+import com.oying.modules.system.domain.dto.JobQueryCriteria;
+import com.oying.modules.system.mapper.JobMapper;
+import com.oying.modules.system.mapper.UserMapper;
+import com.oying.modules.system.service.JobService;
+import com.oying.utils.*;
+import lombok.RequiredArgsConstructor;
+import com.oying.exception.BadRequestException;
+import com.oying.exception.EntityExistException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+* @author Z
+* @date 2019-03-29
+*/
+@Service
+@RequiredArgsConstructor
+public class JobServiceImpl extends ServiceImpl<JobMapper, Job> implements JobService {
+
+    private final JobMapper jobMapper;
+    private final RedisUtils redisUtils;
+    private final UserMapper userMapper;
+
+    @Override
+    public PageResult<Job> queryAll(JobQueryCriteria criteria, Page<Object> page) {
+        return PageUtil.toPage(jobMapper.findAll(criteria, page));
+    }
+
+    @Override
+    public List<Job> queryAll(JobQueryCriteria criteria) {
+        return jobMapper.findAll(criteria);
+    }
+
+    @Override
+    public Job findById(Long id) {
+        String key = CacheKey.JOB_ID + id;
+        Job job = redisUtils.get(key, Job.class);
+        if(job == null){
+            job = getById(id);
+            redisUtils.set(key, job, 1, TimeUnit.DAYS);
+        }
+        return job;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void create(Job resources) {
+        Job job = jobMapper.findByName(resources.getName());
+        if(job != null){
+            throw new EntityExistException(Job.class,"name",resources.getName());
+        }
+        save(resources);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void update(Job resources) {
+        Job job = getById(resources.getId());
+        Job old = jobMapper.findByName(resources.getName());
+        if(old != null && !old.getId().equals(resources.getId())){
+            throw new EntityExistException(Job.class,"name",resources.getName());
+        }
+        resources.setId(job.getId());
+        saveOrUpdate(resources);
+        // 删除缓存
+        delCaches(resources.getId());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delete(Set<Long> ids) {
+        removeBatchByIds(ids);
+        // 删除缓存
+        ids.forEach(this::delCaches);
+    }
+
+    @Override
+    public void download(List<Job> jobs, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (Job job : jobs) {
+            Map<String,Object> map = new LinkedHashMap<>();
+            map.put("岗位名称", job.getName());
+            map.put("岗位状态", job.getEnabled() ? "启用" : "停用");
+            map.put("创建日期", job.getCreateTime());
+            list.add(map);
+        }
+        FileUtil.downloadExcel(list, response);
+    }
+
+    @Override
+    public void verification(Set<Long> ids) {
+        if(userMapper.countByJobs(ids) > 0){
+            throw new BadRequestException("所选的岗位中存在用户关联,请解除关联再试!");
+        }
+    }
+
+    public void delCaches(Long id){
+        redisUtils.del(CacheKey.JOB_ID + id);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/impl/MenuServiceImpl.java b/oying-system/src/main/java/com/oying/modules/system/service/impl/MenuServiceImpl.java
new file mode 100644
index 0000000..d474a3f
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/impl/MenuServiceImpl.java
@@ -0,0 +1,351 @@
+package com.oying.modules.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.oying.modules.system.domain.Menu;
+import com.oying.modules.system.domain.Role;
+import com.oying.modules.system.domain.User;
+import com.oying.modules.system.domain.dto.MenuMetaVo;
+import com.oying.modules.system.domain.dto.MenuQueryCriteria;
+import com.oying.modules.system.domain.dto.MenuVo;
+import com.oying.modules.system.mapper.MenuMapper;
+import com.oying.modules.system.mapper.RoleMenuMapper;
+import com.oying.modules.system.mapper.UserMapper;
+import com.oying.modules.system.service.MenuService;
+import com.oying.modules.system.service.RoleService;
+import lombok.RequiredArgsConstructor;
+import com.oying.utils.CacheKey;
+import com.oying.utils.FileUtil;
+import com.oying.utils.RedisUtils;
+import com.oying.utils.StringUtils;
+import com.oying.exception.BadRequestException;
+import com.oying.exception.EntityExistException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * @author Z
+ */
+@Service
+@RequiredArgsConstructor
+public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
+
+    private final MenuMapper menuMapper;
+    private final RoleMenuMapper roleMenuMapper;
+    private final UserMapper userMapper;
+    private final RoleService roleService;
+    private final RedisUtils redisUtils;
+
+    private static final String HTTP_PRE = "http://";
+    private static final String HTTPS_PRE = "https://";
+    private static final String YES_STR = "是";
+    private static final String NO_STR = "否";
+    private static final String BAD_REQUEST = "外链必须以http://或者https://开头";
+
+    @Override
+    public List<Menu> queryAll(MenuQueryCriteria criteria, Boolean isQuery) throws Exception {
+        if(Boolean.TRUE.equals(isQuery)){
+            criteria.setPidIsNull(true);
+            List<Field> fields = StringUtils.getAllFields(criteria.getClass(), new ArrayList<>());
+            for (Field field : fields) {
+                //设置对象的访问权限,保证对private的属性的访问
+                field.setAccessible(true);
+                Object val = field.get(criteria);
+                if("pidIsNull".equals(field.getName())){
+                    continue;
+                }
+                // 如果有查询条件,则不指定pidIsNull
+                if (ObjectUtil.isNotNull(val)) {
+                    criteria.setPidIsNull(null);
+                    break;
+                }
+            }
+        }
+        return menuMapper.findAll(criteria);
+    }
+
+    @Override
+    public Menu findById(long id) {
+        String key = CacheKey.MENU_ID + id;
+        Menu menu = redisUtils.get(key, Menu.class);
+        if(menu == null){
+            menu = getById(id);
+            redisUtils.set(key, menu, 1, TimeUnit.DAYS);
+        }
+        return menu;
+    }
+
+    /**
+     * 用户角色改变时需清理缓存
+     * @param currentUserId /
+     * @return /
+     */
+    @Override
+    public List<Menu> findByUser(Long currentUserId) {
+        String key = CacheKey.MENU_USER + currentUserId;
+        List<Menu> menus = redisUtils.getList(key, Menu.class);
+        if (CollUtil.isEmpty(menus)){
+            List<Role> roles = roleService.findByUsersId(currentUserId);
+            Set<Long> roleIds = roles.stream().map(Role::getId).collect(Collectors.toSet());
+            menus = new ArrayList<>(menuMapper.findByRoleIdsAndTypeNot(roleIds, 2));
+            redisUtils.set(key, menus, 1, TimeUnit.DAYS);
+        }
+        return menus;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void create(Menu resources) {
+        if(menuMapper.findByTitle(resources.getTitle()) != null){
+            throw new EntityExistException(Menu.class,"title",resources.getTitle());
+        }
+        if(StringUtils.isNotBlank(resources.getComponentName())){
+            if(menuMapper.findByComponentName(resources.getComponentName()) != null){
+                throw new EntityExistException(Menu.class,"componentName",resources.getComponentName());
+            }
+        }
+        if (Long.valueOf(0L).equals(resources.getPid())) {
+            resources.setPid(null);
+        }
+        if(resources.getIFrame()){
+            if (!(resources.getPath().toLowerCase().startsWith(HTTP_PRE)||resources.getPath().toLowerCase().startsWith(HTTPS_PRE))) {
+                throw new BadRequestException(BAD_REQUEST);
+            }
+        }
+        save(resources);
+        // 计算子节点数目
+        resources.setSubCount(0);
+        // 更新父节点菜单数目
+        updateSubCnt(resources.getPid());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void update(Menu resources) {
+        if(resources.getId().equals(resources.getPid())) {
+            throw new BadRequestException("上级不能为自己");
+        }
+        Menu menu = getById(resources.getId());
+        if(resources.getIFrame()){
+            if (!(resources.getPath().toLowerCase().startsWith(HTTP_PRE)||resources.getPath().toLowerCase().startsWith(HTTPS_PRE))) {
+                throw new BadRequestException(BAD_REQUEST);
+            }
+        }
+        Menu menu1 = menuMapper.findByTitle(resources.getTitle());
+
+        if(menu1 != null && !menu1.getId().equals(menu.getId())){
+            throw new EntityExistException(Menu.class,"title",resources.getTitle());
+        }
+
+        if(resources.getPid().equals(0L)){
+            resources.setPid(null);
+        }
+
+        // 记录的父节点ID
+        Long oldPid = menu.getPid();
+        Long newPid = resources.getPid();
+
+        if(StringUtils.isNotBlank(resources.getComponentName())){
+            menu1 = menuMapper.findByComponentName(resources.getComponentName());
+            if(menu1 != null && !menu1.getId().equals(menu.getId())){
+                throw new EntityExistException(Menu.class,"componentName",resources.getComponentName());
+            }
+        }
+        menu.setTitle(resources.getTitle());
+        menu.setComponent(resources.getComponent());
+        menu.setPath(resources.getPath());
+        menu.setIcon(resources.getIcon());
+        menu.setIFrame(resources.getIFrame());
+        menu.setPid(resources.getPid());
+        menu.setMenuSort(resources.getMenuSort());
+        menu.setCache(resources.getCache());
+        menu.setHidden(resources.getHidden());
+        menu.setComponentName(resources.getComponentName());
+        menu.setPermission(resources.getPermission());
+        menu.setType(resources.getType());
+        saveOrUpdate(menu);
+        // 计算父级菜单节点数目
+        updateSubCnt(oldPid);
+        updateSubCnt(newPid);
+        // 清理缓存
+        delCaches(resources.getId());
+    }
+
+    @Override
+    public Set<Menu> getChildMenus(List<Menu> menuList, Set<Menu> menuSet) {
+        for (Menu menu : menuList) {
+            menuSet.add(menu);
+            List<Menu> menus = menuMapper.findByPidOrderByMenuSort(menu.getId());
+            if(CollUtil.isNotEmpty(menus)){
+                getChildMenus(menus, menuSet);
+            }
+        }
+        return menuSet;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delete(Set<Menu> menuSet) {
+        for (Menu menu : menuSet) {
+            // 清理缓存
+            delCaches(menu.getId());
+            roleMenuMapper.deleteByMenuId(menu.getId());
+            menuMapper.deleteById(menu.getId());
+            updateSubCnt(menu.getPid());
+        }
+    }
+
+    @Override
+    public List<Menu> getMenus(Long pid) {
+        List<Menu> menus;
+        if(pid != null && !pid.equals(0L)){
+            menus = menuMapper.findByPidOrderByMenuSort(pid);
+        } else {
+            menus = menuMapper.findByPidIsNullOrderByMenuSort();
+        }
+        return menus;
+    }
+
+    @Override
+    public List<Menu> getSuperior(Menu menu, List<Menu> menus) {
+        if(menu.getPid() == null){
+            menus.addAll(menuMapper.findByPidIsNullOrderByMenuSort());
+            return menus;
+        }
+        menus.addAll(menuMapper.findByPidOrderByMenuSort(menu.getPid()));
+        return getSuperior(findById(menu.getPid()), menus);
+    }
+
+    @Override
+    public List<Menu> buildTree(List<Menu> menus) {
+        List<Menu> trees = new ArrayList<>();
+        Set<Long> ids = new HashSet<>();
+        for (Menu menu : menus) {
+            if (menu.getPid() == null) {
+                trees.add(menu);
+            }
+            for (Menu it : menus) {
+                if (menu.getId().equals(it.getPid())) {
+                    if (menu.getChildren() == null) {
+                        menu.setChildren(new ArrayList<>());
+                    }
+                    menu.getChildren().add(it);
+                    ids.add(it.getId());
+                }
+            }
+        }
+        if(CollUtil.isNotEmpty(trees)){
+            trees = menus.stream().filter(s -> !ids.contains(s.getId())).collect(Collectors.toList());
+        }
+        return trees;
+    }
+
+    @Override
+    public List<MenuVo> buildMenus(List<Menu> menus) {
+        List<MenuVo> list = new LinkedList<>();
+        menus.forEach(menu -> {
+                    if (menu!=null){
+                        List<Menu> menuList = menu.getChildren();
+                        MenuVo menuVo = new MenuVo();
+                        menuVo.setName(ObjectUtil.isNotEmpty(menu.getComponentName())  ? menu.getComponentName() : menu.getTitle());
+                        // 一级目录需要加斜杠,不然会报警告
+                        menuVo.setPath(menu.getPid() == null ? "/" + menu.getPath() :menu.getPath());
+                        menuVo.setHidden(menu.getHidden());
+                        // 如果不是外链
+                        if(!menu.getIFrame()){
+                            if(menu.getPid() == null){
+                                menuVo.setComponent(StringUtils.isEmpty(menu.getComponent())?"Layout":menu.getComponent());
+                                // 如果不是一级菜单,并且菜单类型为目录,则代表是多级菜单
+                            }else if(menu.getType() == 0){
+                                menuVo.setComponent(StringUtils.isEmpty(menu.getComponent())?"ParentView":menu.getComponent());
+                            }else if(StringUtils.isNoneBlank(menu.getComponent())){
+                                menuVo.setComponent(menu.getComponent());
+                            }
+                        }
+                        menuVo.setMeta(new MenuMetaVo(menu.getTitle(),menu.getIcon(),!menu.getCache()));
+                        if(CollectionUtil.isNotEmpty(menuList)){
+                            menuVo.setAlwaysShow(true);
+                            menuVo.setRedirect("noredirect");
+                            menuVo.setChildren(buildMenus(menuList));
+                            // 处理是一级菜单并且没有子菜单的情况
+                        } else if(menu.getPid() == null){
+                            MenuVo menuVo1 = getMenuVo(menu, menuVo);
+                            menuVo.setName(null);
+                            menuVo.setMeta(null);
+                            menuVo.setComponent("Layout");
+                            List<MenuVo> list1 = new ArrayList<>();
+                            list1.add(menuVo1);
+                            menuVo.setChildren(list1);
+                        }
+                        list.add(menuVo);
+                    }
+                }
+        );
+        return list;
+    }
+
+    @Override
+    public void download(List<Menu> menus, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (Menu menu : menus) {
+            Map<String,Object> map = new LinkedHashMap<>();
+            map.put("菜单标题", menu.getTitle());
+            map.put("菜单类型", menu.getType() == null ? "目录" : menu.getType() == 1 ? "菜单" : "按钮");
+            map.put("权限标识", menu.getPermission());
+            map.put("外链菜单", menu.getIFrame() ? YES_STR : NO_STR);
+            map.put("菜单可见", menu.getHidden() ? NO_STR : YES_STR);
+            map.put("是否缓存", menu.getCache() ? YES_STR : NO_STR);
+            map.put("创建日期", menu.getCreateTime());
+            list.add(map);
+        }
+        FileUtil.downloadExcel(list, response);
+    }
+
+    private void updateSubCnt(Long menuId){
+        if(menuId != null){
+            int count = menuMapper.countByPid(menuId);
+            menuMapper.updateSubCntById(count, menuId);
+        }
+    }
+
+    /**
+     * 清理缓存
+     * @param id 菜单ID
+     */
+    public void delCaches(Long id){
+        List<User> users = userMapper.findByMenuId(id);
+        redisUtils.del(CacheKey.MENU_ID + id);
+        redisUtils.delByKeys(CacheKey.MENU_USER, users.stream().map(User::getId).collect(Collectors.toSet()));
+        // 清除 Role 缓存
+        List<Role> roles = roleService.findByMenuId(id);
+        redisUtils.delByKeys(CacheKey.ROLE_ID, roles.stream().map(Role::getId).collect(Collectors.toSet()));
+    }
+
+    /**
+     * 获取 MenuVo
+     * @param menu /
+     * @param menuVo /
+     * @return /
+     */
+    private static MenuVo getMenuVo(Menu menu, MenuVo menuVo) {
+        MenuVo menuVo1 = new MenuVo();
+        menuVo1.setMeta(menuVo.getMeta());
+        // 非外链
+        if(!menu.getIFrame()){
+            menuVo1.setPath("index");
+            menuVo1.setName(menuVo.getName());
+            menuVo1.setComponent(menuVo.getComponent());
+        } else {
+            menuVo1.setPath(menu.getPath());
+        }
+        return menuVo1;
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/impl/MonitorServiceImpl.java b/oying-system/src/main/java/com/oying/modules/system/service/impl/MonitorServiceImpl.java
new file mode 100644
index 0000000..88c037d
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/impl/MonitorServiceImpl.java
@@ -0,0 +1,179 @@
+package com.oying.modules.system.service.impl;
+
+import cn.hutool.core.date.BetweenFormatter.Level;
+import cn.hutool.core.date.DateUtil;
+import com.oying.modules.system.service.MonitorService;
+import lombok.extern.slf4j.Slf4j;
+import com.oying.utils.ElConstant;
+import com.oying.utils.FileUtil;
+import com.oying.utils.StringUtils;
+import org.springframework.stereotype.Service;
+import oshi.SystemInfo;
+import oshi.hardware.*;
+import oshi.software.os.FileSystem;
+import oshi.software.os.OSFileStore;
+import oshi.software.os.OperatingSystem;
+import oshi.util.FormatUtil;
+import oshi.util.Util;
+import java.lang.management.ManagementFactory;
+import java.text.DecimalFormat;
+import java.util.*;
+
+/**
+* @author Z
+* @date 2020-05-02
+*/
+@Slf4j
+@Service
+public class MonitorServiceImpl implements MonitorService {
+
+    private final DecimalFormat df = new DecimalFormat("0.00");
+
+    @Override
+    public Map<String,Object> getServers(){
+        Map<String, Object> resultMap = new LinkedHashMap<>(8);
+        try {
+            SystemInfo si = new SystemInfo();
+            OperatingSystem os = si.getOperatingSystem();
+            HardwareAbstractionLayer hal = si.getHardware();
+            // 系统信息
+            resultMap.put("sys", getSystemInfo(os));
+            // cpu 信息
+            resultMap.put("cpu", getCpuInfo(hal.getProcessor()));
+            // 内存信息
+            resultMap.put("memory", getMemoryInfo(hal.getMemory()));
+            // 交换区信息
+            resultMap.put("swap", getSwapInfo(hal.getMemory()));
+            // 磁盘
+            resultMap.put("disk", getDiskInfo(os));
+            resultMap.put("time", DateUtil.format(new Date(), "HH:mm:ss"));
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return resultMap;
+    }
+
+    /**
+     * 获取磁盘信息
+     * @return /
+     */
+    private Map<String,Object> getDiskInfo(OperatingSystem os) {
+        Map<String,Object> diskInfo = new LinkedHashMap<>();
+        FileSystem fileSystem = os.getFileSystem();
+        List<OSFileStore> fsArray = fileSystem.getFileStores();
+        String osName = System.getProperty("os.name");
+        long available = 0, total = 0;
+        for (OSFileStore fs : fsArray){
+            // windows 需要将所有磁盘分区累加,linux 和 mac 直接累加会出现磁盘重复的问题,待修复
+            if(osName.toLowerCase().startsWith(ElConstant.WIN)) {
+                available += fs.getUsableSpace();
+                total += fs.getTotalSpace();
+            } else {
+                available = fs.getUsableSpace();
+                total = fs.getTotalSpace();
+                break;
+            }
+        }
+        long used = total - available;
+        diskInfo.put("total", total > 0 ? FileUtil.getSize(total) : "?");
+        diskInfo.put("available", FileUtil.getSize(available));
+        diskInfo.put("used", FileUtil.getSize(used));
+        if(total != 0){
+            diskInfo.put("usageRate", df.format(used/(double)total * 100));
+        } else {
+            diskInfo.put("usageRate", 0);
+        }
+        return diskInfo;
+    }
+
+    /**
+     * 获取交换区信息
+     * @param memory /
+     * @return /
+     */
+    private Map<String,Object> getSwapInfo(GlobalMemory memory) {
+        Map<String,Object> swapInfo = new LinkedHashMap<>();
+        VirtualMemory virtualMemory = memory.getVirtualMemory();
+        long total = virtualMemory.getSwapTotal();
+        long used = virtualMemory.getSwapUsed();
+        swapInfo.put("total", FormatUtil.formatBytes(total));
+        swapInfo.put("used", FormatUtil.formatBytes(used));
+        swapInfo.put("available", FormatUtil.formatBytes(total - used));
+        if(used == 0){
+            swapInfo.put("usageRate", 0);
+        } else {
+            swapInfo.put("usageRate", df.format(used/(double)total * 100));
+        }
+        return swapInfo;
+    }
+
+    /**
+     * 获取内存信息
+     * @param memory /
+     * @return /
+     */
+    private Map<String,Object> getMemoryInfo(GlobalMemory memory) {
+        Map<String,Object> memoryInfo = new LinkedHashMap<>();
+        memoryInfo.put("total", FormatUtil.formatBytes(memory.getTotal()));
+        memoryInfo.put("available", FormatUtil.formatBytes(memory.getAvailable()));
+        memoryInfo.put("used", FormatUtil.formatBytes(memory.getTotal() - memory.getAvailable()));
+        memoryInfo.put("usageRate", df.format((memory.getTotal() - memory.getAvailable())/(double)memory.getTotal() * 100));
+        return memoryInfo;
+    }
+
+    /**
+     * 获取Cpu相关信息
+     * @param processor /
+     * @return /
+     */
+    private Map<String,Object> getCpuInfo(CentralProcessor processor) {
+        Map<String,Object> cpuInfo = new LinkedHashMap<>();
+        cpuInfo.put("name", processor.getProcessorIdentifier().getName());
+        cpuInfo.put("package", processor.getPhysicalPackageCount() + "个物理CPU");
+        cpuInfo.put("core", processor.getPhysicalProcessorCount() + "个物理核心");
+        cpuInfo.put("coreNumber", processor.getPhysicalProcessorCount());
+        cpuInfo.put("logic", processor.getLogicalProcessorCount() + "个逻辑CPU");
+        // CPU信息
+        long[] prevTicks = processor.getSystemCpuLoadTicks();
+        // 默认等待300毫秒...
+        long time = 300;
+        Util.sleep(time);
+        long[] ticks = processor.getSystemCpuLoadTicks();
+        while (Arrays.toString(prevTicks).equals(Arrays.toString(ticks)) && time < 1000){
+            time += 25;
+            Util.sleep(25);
+            ticks = processor.getSystemCpuLoadTicks();
+        }
+        long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()];
+        long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()];
+        long sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
+        long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()];
+        long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
+        long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()];
+        long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
+        long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()];
+        long totalCpu = user + nice + sys + idle + iowait + irq + softirq + steal;
+        cpuInfo.put("used", df.format(100d * user / totalCpu + 100d * sys / totalCpu));
+        cpuInfo.put("idle", df.format(100d * idle / totalCpu));
+        return cpuInfo;
+    }
+
+    /**
+     * 获取系统相关信息,系统、运行天数、系统IP
+     * @param os /
+     * @return /
+     */
+    private Map<String,Object> getSystemInfo(OperatingSystem os){
+        Map<String,Object> systemInfo = new LinkedHashMap<>();
+        // jvm 运行时间
+        long time = ManagementFactory.getRuntimeMXBean().getStartTime();
+        Date date = new Date(time);
+        // 计算项目运行时间
+        String formatBetween = DateUtil.formatBetween(date, new Date(), Level.HOUR);
+        // 系统信息
+        systemInfo.put("os", os.toString());
+        systemInfo.put("day", formatBetween);
+        systemInfo.put("ip", StringUtils.getLocalIp());
+        return systemInfo;
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/impl/RoleServiceImpl.java b/oying-system/src/main/java/com/oying/modules/system/service/impl/RoleServiceImpl.java
new file mode 100644
index 0000000..91b8820
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/impl/RoleServiceImpl.java
@@ -0,0 +1,226 @@
+package com.oying.modules.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.oying.modules.security.service.UserCacheManager;
+import com.oying.modules.security.service.dto.AuthorityDto;
+import com.oying.modules.system.domain.Menu;
+import com.oying.modules.system.domain.Role;
+import com.oying.modules.system.domain.User;
+import com.oying.modules.system.domain.dto.RoleQueryCriteria;
+import com.oying.modules.system.mapper.RoleDeptMapper;
+import com.oying.modules.system.mapper.RoleMapper;
+import com.oying.modules.system.mapper.RoleMenuMapper;
+import com.oying.modules.system.mapper.UserMapper;
+import com.oying.utils.*;
+import lombok.RequiredArgsConstructor;
+import com.oying.exception.BadRequestException;
+import com.oying.exception.EntityExistException;
+import com.oying.modules.system.service.RoleService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * @author Z
+ * @date 2018-12-03
+ */
+@Service
+@RequiredArgsConstructor
+public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
+
+    private final RoleMapper roleMapper;
+    private final RoleDeptMapper roleDeptMapper;
+    private final RoleMenuMapper roleMenuMapper;
+    private final RedisUtils redisUtils;
+    private final UserMapper userMapper;
+    private final UserCacheManager userCacheManager;
+
+    @Override
+    public List<Role> queryAll() {
+        return roleMapper.queryAll();
+    }
+
+    @Override
+    public List<Role> queryAll(RoleQueryCriteria criteria) {
+        return roleMapper.findAll(criteria);
+    }
+
+    @Override
+    public PageResult<Role> queryAll(RoleQueryCriteria criteria, Page<Object> page) {
+        criteria.setOffset(page.offset());
+        List<Role> roles = roleMapper.findAll(criteria);
+        Long total = roleMapper.countAll(criteria);
+        return PageUtil.toPage(roles, total);
+    }
+
+    @Override
+    public Role findById(long id) {
+        String key = CacheKey.ROLE_ID + id;
+        Role role = redisUtils.get(key, Role.class);
+        if (role == null) {
+            role = roleMapper.findById(id);
+            redisUtils.set(key, role, 1, TimeUnit.DAYS);
+        }
+        return role;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void create(Role resources) {
+        if (roleMapper.findByName(resources.getName()) != null) {
+            throw new EntityExistException(Role.class, "username", resources.getName());
+        }
+        save(resources);
+        // 判断是否有机构数据,若有,则需创建关联
+        if (CollectionUtil.isNotEmpty(resources.getDepts())) {
+            roleDeptMapper.insertData(resources.getId(), resources.getDepts());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void update(Role resources) {
+        Role role = getById(resources.getId());
+        Role role1 = roleMapper.findByName(resources.getName());
+        if (role1 != null && !role1.getId().equals(role.getId())) {
+            throw new EntityExistException(Role.class, "username", resources.getName());
+        }
+        role.setName(resources.getName());
+        role.setDescription(resources.getDescription());
+        role.setDataScope(resources.getDataScope());
+        role.setDepts(resources.getDepts());
+        role.setLevel(resources.getLevel());
+        // 更新
+        saveOrUpdate(role);
+        // 删除关联机构数据
+        roleDeptMapper.deleteByRoleId(resources.getId());
+        // 判断是否有机构数据,若有,则需更新关联
+        if (CollectionUtil.isNotEmpty(resources.getDepts())) {
+            roleDeptMapper.insertData(resources.getId(), resources.getDepts());
+        }
+        // 更新相关缓存
+        delCaches(role.getId(), null);
+    }
+
+    @Override
+    public void updateMenu(Role role) {
+        List<User> users = userMapper.findByRoleId(role.getId());
+        // 更新菜单
+        roleMenuMapper.deleteByRoleId(role.getId());
+        // 判断是否为空
+        if(CollUtil.isNotEmpty(role.getMenus())){
+            roleMenuMapper.insertData(role.getId(), role.getMenus());
+        }
+        // 更新缓存
+        delCaches(role.getId(), users);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delete(Set<Long> ids) {
+        for (Long id : ids) {
+            // 更新相关缓存
+            delCaches(id, null);
+        }
+        removeBatchByIds(ids);
+        // 删除角色机构关联数据、角色菜单关联数据
+        roleDeptMapper.deleteByRoleIds(ids);
+        roleMenuMapper.deleteByRoleIds(ids);
+    }
+
+    @Override
+    public List<Role> findByUsersId(Long userId) {
+        String key = CacheKey.ROLE_USER + userId;
+        List<Role> roles = redisUtils.getList(key, Role.class);
+        if (CollUtil.isEmpty(roles)) {
+            roles = roleMapper.findByUserId(userId);
+            redisUtils.set(key, roles, 1, TimeUnit.DAYS);
+        }
+        return roles;
+    }
+
+    @Override
+    public Integer findByRoles(Set<Role> roles) {
+        if (CollUtil.isEmpty(roles)) {
+            return Integer.MAX_VALUE;
+        }
+        Set<Role> roleSet = new HashSet<>();
+        for (Role role : roles) {
+            roleSet.add(findById(role.getId()));
+        }
+        return Collections.min(roleSet.stream().map(Role::getLevel).collect(Collectors.toList()));
+    }
+
+    @Override
+    public List<AuthorityDto> buildPermissions(User user) {
+        String key = CacheKey.ROLE_AUTH + user.getId();
+        List<AuthorityDto> authorityDtos = redisUtils.getList(key, AuthorityDto.class);
+        if (CollUtil.isEmpty(authorityDtos)) {
+            Set<String> permissions = new HashSet<>();
+            // 如果是管理员直接返回
+            if (user.getIsAdmin()) {
+                permissions.add("admin");
+                return permissions.stream().map(AuthorityDto::new)
+                        .collect(Collectors.toList());
+            }
+            List<Role> roles = roleMapper.findByUserId(user.getId());
+            permissions = roles.stream().flatMap(role -> role.getMenus().stream())
+                    .map(Menu::getPermission)
+                    .filter(StringUtils::isNotBlank).collect(Collectors.toSet());
+            authorityDtos = permissions.stream().map(AuthorityDto::new)
+                    .collect(Collectors.toList());
+            redisUtils.set(key, authorityDtos, 1, TimeUnit.HOURS);
+        }
+        return authorityDtos;
+    }
+
+    @Override
+    public void download(List<Role> roles, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (Role role : roles) {
+            Map<String, Object> map = new LinkedHashMap<>();
+            map.put("角色名称", role.getName());
+            map.put("角色级别", role.getLevel());
+            map.put("描述", role.getDescription());
+            map.put("创建日期", role.getCreateTime());
+            list.add(map);
+        }
+        FileUtil.downloadExcel(list, response);
+    }
+
+    @Override
+    public void verification(Set<Long> ids) {
+        if (userMapper.countByRoles(ids) > 0) {
+            throw new BadRequestException("所选角色存在用户关联,请解除关联再试!");
+        }
+    }
+
+    @Override
+    public List<Role> findByMenuId(Long menuId) {
+        return roleMapper.findByMenuId(menuId);
+    }
+
+    /**
+     * 清理缓存
+     * @param id /
+     */
+    public void delCaches(Long id, List<User> users) {
+        users = CollectionUtil.isEmpty(users) ? userMapper.findByRoleId(id) : users;
+        if (CollectionUtil.isNotEmpty(users)) {
+            users.forEach(item -> userCacheManager.cleanUserCache(item.getUsername()));
+            Set<Long> userIds = users.stream().map(User::getId).collect(Collectors.toSet());
+            redisUtils.delByKeys(CacheKey.DATA_USER, userIds);
+            redisUtils.delByKeys(CacheKey.MENU_USER, userIds);
+            redisUtils.delByKeys(CacheKey.ROLE_AUTH, userIds);
+            redisUtils.delByKeys(CacheKey.ROLE_USER, userIds);
+        }
+        redisUtils.del(CacheKey.ROLE_ID + id);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/impl/UserServiceImpl.java b/oying-system/src/main/java/com/oying/modules/system/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000..2a8fe44
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/impl/UserServiceImpl.java
@@ -0,0 +1,279 @@
+package com.oying.modules.system.service.impl;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.oying.modules.security.service.OnlineUserService;
+import com.oying.modules.security.service.UserCacheManager;
+import com.oying.modules.system.domain.Job;
+import com.oying.modules.system.domain.Role;
+import com.oying.modules.system.domain.User;
+import com.oying.modules.system.domain.dto.UserQueryCriteria;
+import com.oying.modules.system.mapper.UserJobMapper;
+import com.oying.modules.system.mapper.UserMapper;
+import com.oying.modules.system.mapper.UserRoleMapper;
+import com.oying.modules.system.service.UserService;
+import com.oying.utils.*;
+import lombok.RequiredArgsConstructor;
+import com.oying.config.properties.FileProperties;
+import com.oying.exception.BadRequestException;
+import com.oying.exception.EntityExistException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotBlank;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * @author Z
+ * @date 2018-11-23
+ */
+@Service
+@RequiredArgsConstructor
+public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
+
+    private final UserMapper userMapper;
+    private final UserJobMapper userJobMapper;
+    private final UserRoleMapper userRoleMapper;
+    private final FileProperties properties;
+    private final RedisUtils redisUtils;
+    private final UserCacheManager userCacheManager;
+    private final OnlineUserService onlineUserService;
+
+    @Override
+    public PageResult<User> queryAll(UserQueryCriteria criteria, Page<Object> page) {
+        criteria.setOffset(page.offset());
+        List<User> users = userMapper.findAll(criteria);
+        Long total = userMapper.countAll(criteria);
+        return PageUtil.toPage(users, total);
+    }
+
+    @Override
+    public List<User> queryAll(UserQueryCriteria criteria) {
+        return userMapper.findAll(criteria);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public User findById(long id) {
+        String key = CacheKey.USER_ID + id;
+        User user = redisUtils.get(key, User.class);
+        if (user == null) {
+            user = getById(id);
+            redisUtils.set(key, user, 1, TimeUnit.DAYS);
+        }
+        return user;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void create(User resources) {
+        resources.setDeptId(resources.getDept().getId());
+        if (userMapper.findByUsername(resources.getUsername()) != null) {
+            throw new EntityExistException(User.class, "username", resources.getUsername());
+        }
+        if (userMapper.findByEmail(resources.getEmail()) != null) {
+            throw new EntityExistException(User.class, "email", resources.getEmail());
+        }
+        if (userMapper.findByPhone(resources.getPhone()) != null) {
+            throw new EntityExistException(User.class, "phone", resources.getPhone());
+        }
+        save(resources);
+        // 保存用户岗位
+        userJobMapper.insertData(resources.getId(), resources.getJobs());
+        // 保存用户角色
+        userRoleMapper.insertData(resources.getId(), resources.getRoles());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void update(User resources) throws Exception {
+        User user = getById(resources.getId());
+        User user1 = userMapper.findByUsername(resources.getUsername());
+        User user2 = userMapper.findByEmail(resources.getEmail());
+        User user3 = userMapper.findByPhone(resources.getPhone());
+        if (user1 != null && !user.getId().equals(user1.getId())) {
+            throw new EntityExistException(User.class, "username", resources.getUsername());
+        }
+        if (user2 != null && !user.getId().equals(user2.getId())) {
+            throw new EntityExistException(User.class, "email", resources.getEmail());
+        }
+        if (user3 != null && !user.getId().equals(user3.getId())) {
+            throw new EntityExistException(User.class, "phone", resources.getPhone());
+        }
+        // 如果用户的角色改变
+        if (!resources.getRoles().equals(user.getRoles())) {
+            redisUtils.del(CacheKey.DATA_USER + resources.getId());
+            redisUtils.del(CacheKey.MENU_USER + resources.getId());
+            redisUtils.del(CacheKey.ROLE_AUTH + resources.getId());
+            redisUtils.del(CacheKey.ROLE_USER + resources.getId());
+        }
+        // 修改机构会影响 数据权限
+        if (!Objects.equals(resources.getDept(),user.getDept())) {
+            redisUtils.del(CacheKey.DATA_USER + resources.getId());
+        }
+        // 如果用户被禁用,则清除用户登录信息
+        if(!resources.getEnabled()){
+            onlineUserService.kickOutForUsername(resources.getUsername());
+        }
+        user.setDeptId(resources.getDept().getId());
+        user.setUsername(resources.getUsername());
+        user.setEmail(resources.getEmail());
+        user.setEnabled(resources.getEnabled());
+        user.setRoles(resources.getRoles());
+        user.setDept(resources.getDept());
+        user.setJobs(resources.getJobs());
+        user.setPhone(resources.getPhone());
+        user.setNickName(resources.getNickName());
+        user.setGender(resources.getGender());
+        saveOrUpdate(user);
+        // 清除缓存
+        delCaches(user.getId(), user.getUsername());
+        // 更新用户岗位
+        userJobMapper.deleteByUserId(resources.getId());
+        userJobMapper.insertData(resources.getId(), resources.getJobs());
+        // 更新用户角色
+        userRoleMapper.deleteByUserId(resources.getId());
+        userRoleMapper.insertData(resources.getId(), resources.getRoles());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateCenter(User resources) {
+        User user = getById(resources.getId());
+        User user1 = userMapper.findByPhone(resources.getPhone());
+        if (user1 != null && !user.getId().equals(user1.getId())) {
+            throw new EntityExistException(User.class, "phone", resources.getPhone());
+        }
+        user.setNickName(resources.getNickName());
+        user.setPhone(resources.getPhone());
+        user.setGender(resources.getGender());
+        saveOrUpdate(user);
+        // 清理缓存
+        delCaches(user.getId(), user.getUsername());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delete(Set<Long> ids) {
+        for (Long id : ids) {
+            // 清理缓存
+            User user = getById(id);
+            delCaches(user.getId(), user.getUsername());
+        }
+        userMapper.deleteBatchIds(ids);
+        // 删除用户岗位
+        userJobMapper.deleteByUserIds(ids);
+        // 删除用户角色
+        userRoleMapper.deleteByUserIds(ids);
+    }
+
+    @Override
+    public User findByName(String userName) {
+        return userMapper.findByUsername(userName);
+    }
+
+    @Override
+    public User getLoginData(String userName) {
+        return userMapper.findByUsername(userName);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updatePass(String username, String pass) {
+        userMapper.updatePass(username, pass, new Date());
+        flushCache(username);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void resetPwd(Set<Long> ids, String pwd) {
+        List<User> users = userMapper.selectBatchIds(ids);
+        // 清除缓存
+        users.forEach(user -> {
+            // 清除缓存
+            flushCache(user.getUsername());
+            // 强制退出
+            onlineUserService.kickOutForUsername(user.getUsername());
+        });
+        // 重置密码
+        userMapper.resetPwd(ids, pwd);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Map<String, String> updateAvatar(MultipartFile multipartFile) {
+        // 文件大小验证
+        FileUtil.checkSize(properties.getAvatarMaxSize(), multipartFile.getSize());
+        // 验证文件上传的格式
+        String image = "gif jpg png jpeg";
+        String fileType = FileUtil.getExtensionName(multipartFile.getOriginalFilename());
+        if(fileType != null && !image.contains(fileType)){
+            throw new BadRequestException("文件格式错误!, 仅支持 " + image +" 格式");
+        }
+        User user = userMapper.findByUsername(SecurityUtils.getCurrentUsername());
+        String oldPath = user.getAvatarPath();
+        File file = FileUtil.upload(multipartFile, properties.getPath().getAvatar());
+        user.setAvatarPath(Objects.requireNonNull(file).getPath());
+        user.setAvatarName(file.getName());
+        saveOrUpdate(user);
+        if (StringUtils.isNotBlank(oldPath)) {
+            FileUtil.del(oldPath);
+        }
+        @NotBlank String username = user.getUsername();
+        flushCache(username);
+        return new HashMap<String, String>(1) {{
+            put("avatar", file.getName());
+        }};
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateEmail(String username, String email) {
+        userMapper.updateEmail(username, email);
+        flushCache(username);
+    }
+
+    @Override
+    public void download(List<User> users, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (User user : users) {
+            List<String> roles = user.getRoles().stream().map(Role::getName).collect(Collectors.toList());
+            Map<String, Object> map = new LinkedHashMap<>();
+            map.put("用户名", user.getUsername());
+            map.put("角色", roles);
+            map.put("机构", user.getDept().getName());
+            map.put("岗位", user.getJobs().stream().map(Job::getName).collect(Collectors.toList()));
+            map.put("邮箱", user.getEmail());
+            map.put("状态", user.getEnabled() ? "启用" : "禁用");
+            map.put("手机号码", user.getPhone());
+            map.put("修改密码的时间", user.getPwdResetTime());
+            map.put("创建日期", user.getCreateTime());
+            list.add(map);
+        }
+        FileUtil.downloadExcel(list, response);
+    }
+
+    /**
+     * 清理缓存
+     *
+     * @param id /
+     */
+    public void delCaches(Long id, String username) {
+        redisUtils.del(CacheKey.USER_ID + id);
+        flushCache(username);
+    }
+
+    /**
+     * 清理 登陆时 用户缓存信息
+     *
+     * @param username /
+     */
+    private void flushCache(String username) {
+        userCacheManager.cleanUserCache(username);
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/system/service/impl/VerifyServiceImpl.java b/oying-system/src/main/java/com/oying/modules/system/service/impl/VerifyServiceImpl.java
new file mode 100644
index 0000000..0de38c2
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/system/service/impl/VerifyServiceImpl.java
@@ -0,0 +1,65 @@
+package com.oying.modules.system.service.impl;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.extra.template.Template;
+import cn.hutool.extra.template.TemplateConfig;
+import cn.hutool.extra.template.TemplateEngine;
+import cn.hutool.extra.template.TemplateUtil;
+import com.oying.modules.system.service.VerifyService;
+import lombok.RequiredArgsConstructor;
+import com.oying.domain.dto.EmailDto;
+import com.oying.exception.BadRequestException;
+import com.oying.utils.RedisUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.util.Collections;
+
+/**
+ * @author Z
+ * @date 2018-12-26
+ */
+@Service
+@RequiredArgsConstructor
+public class VerifyServiceImpl implements VerifyService {
+
+    @Value("${code.expiration}")
+    private Long expiration;
+    private final RedisUtils redisUtils;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public EmailDto sendEmail(String email, String key) {
+        EmailDto emailDto;
+        String content;
+        String redisKey = key + email;
+        // 如果不存在有效的验证码,就创建一个新的
+        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
+        Template template = engine.getTemplate("email.ftl");
+        String oldCode =  redisUtils.get(redisKey, String.class);
+        if(oldCode == null){
+            String code = RandomUtil.randomNumbers (6);
+            // 存入缓存
+            if(!redisUtils.set(redisKey, code, expiration)){
+                throw new BadRequestException("服务异常,请联系网站负责人");
+            }
+            content = template.render(Dict.create().set("code",code));
+            // 存在就再次发送原来的验证码
+        } else {
+            content = template.render(Dict.create().set("code",oldCode));
+        }
+        emailDto = new EmailDto(Collections.singletonList(email),"OYING 后台管理系统",content);
+        return emailDto;
+    }
+
+    @Override
+    public void validated(String key, String code) {
+        String value = redisUtils.get(key, String.class);
+        if(value == null || !value.equals(code)){
+            throw new BadRequestException("无效验证码");
+        } else {
+            redisUtils.del(key);
+        }
+    }
+}
diff --git a/oying-system/src/main/java/com/oying/sysrunner/SystemRunner.java b/oying-system/src/main/java/com/oying/sysrunner/SystemRunner.java
new file mode 100644
index 0000000..8941c77
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/sysrunner/SystemRunner.java
@@ -0,0 +1,22 @@
+package com.oying.sysrunner;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Z
+ * @description 程序启动后处理数据
+ * @date 2025-01-13
+ **/
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SystemRunner implements ApplicationRunner {
+
+    @Override
+    public void run(ApplicationArguments args) {
+    }
+}
diff --git a/oying-system/src/main/resources/banner.txt b/oying-system/src/main/resources/banner.txt
new file mode 100644
index 0000000..d15366e
--- /dev/null
+++ b/oying-system/src/main/resources/banner.txt
@@ -0,0 +1,15 @@
+                              ,--,
+           ,---.            ,--.'|         ,---,
+          '   ,'\           |  |,      ,-+-. /  |  ,----._,.
+         /   /   |      .--,`--'_     ,--.'|'   | /   /  ' /
+        .   ; ,. :    /_ ./|,' ,'|   |   |  ,"' ||   :     |
+        '   | |: : , ' , ' :'  | |   |   | /  | ||   | .\  .
+        '   | .; :/___/ \: ||  | :   |   | |  | |.   ; ';  |
+        |   :    | .  \  ' |'  : |__ |   | |  |/ '   .   . |
+         \   \  /   \  ;   :|  | '.'||   | |--'   `---`-'| |
+          `----'     \  \  ;;  :    ;|   |/       .'__/\_: |
+                      :  \  \  ,   / '---'        |   :    :
+                       \  ' ;---`-'                \   \  /
+                        `--`                        `--`-'
+
+Spring Boot Version: (${spring-boot.version}),EL-ADMIN version: (1.1)
diff --git a/oying-system/src/main/resources/config/application-dev.yml b/oying-system/src/main/resources/config/application-dev.yml
new file mode 100644
index 0000000..1880cf2
--- /dev/null
+++ b/oying-system/src/main/resources/config/application-dev.yml
@@ -0,0 +1,118 @@
+#配置数据源
+spring:
+  datasource:
+    druid:
+      db-type: com.alibaba.druid.pool.DruidDataSource
+      driverClassName: com.p6spy.engine.spy.P6SpyDriver
+      url: jdbc:p6spy:mysql://118.145.136.116:3306/oying?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
+      username: oying
+      password: Ik52981698
+      # 初始连接数,建议设置为与最小空闲连接数相同
+      initial-size: 20
+      # 最小空闲连接数,保持足够的空闲连接以应对请求
+      min-idle: 20
+      # 最大连接数,根据并发需求适当增加
+      max-active: 50
+      # 获取连接超时时间(毫秒),调整以满足响应时间要求
+      max-wait: 3000
+      # 启用KeepAlive机制,保持长连接
+      keep-alive: true
+      # 连接有效性检测间隔时间(毫秒),定期检查连接的健康状态
+      time-between-eviction-runs-millis: 60000
+      # 连接在池中最小生存时间(毫秒),确保连接在池中至少存在一段时间
+      min-evictable-idle-time-millis: 300000
+      # 连接在池中最大生存时间(毫秒),防止连接在池中停留过长
+      max-evictable-idle-time-millis: 900000
+      # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除
+      test-while-idle: true
+      # 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个
+      test-on-borrow: true
+      # 是否在归还到池中前进行检验
+      test-on-return: false
+      # 停用 com_ping 探活机制
+      use-ping-method: false
+      # 检测连接是否有效
+      validation-query: SELECT 1
+      # 配置监控统计
+      webStatFilter:
+        enabled: true
+      stat-view-servlet:
+        enabled: true
+        url-pattern: /druid/*
+        reset-enable: false
+      filter:
+        stat:
+          enabled: true
+          # 记录慢SQL
+          log-slow-sql: true
+          slow-sql-millis: 2000
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+
+# 登录相关配置
+login:
+  #  是否限制单用户登录
+  single-login: false
+  # Redis用户登录缓存配置
+  user-cache:
+    # 存活时间/秒
+    idle-time: 21600
+  #  验证码
+  code:
+    #  验证码类型配置 查看 LoginProperties 类
+    code-type: SPEC
+    #  登录图形验证码有效时间/分钟
+    expiration: 2
+    #  验证码高度
+    width: 111
+    #  验证码宽度
+    height: 36
+    # 内容长度
+    length: 4
+    # 字体名称,为空则使用默认字体
+    font-name:
+    # 字体大小
+    font-size: 25
+
+#jwt
+jwt:
+  header: Authorization
+  # 令牌前缀
+  token-start-with: Bearer
+  # 必须使用最少88位的Base64对该令牌进行编码
+  base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
+  # 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html
+  token-validity-in-seconds: 14400000
+  # 在线用户key
+  online-key: "online_token:"
+  # 验证码
+  code-key: "captcha_code:"
+  # token 续期检查时间范围(默认30分钟,单位毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期
+  detect: 1800000
+  # 续期时间范围,默认1小时,单位毫秒
+  renew: 3600000
+
+#是否允许生成代码,生产环境设置为false
+generator:
+  enabled: true
+
+#是否开启 swagger-ui
+swagger:
+  enabled: true
+
+# 文件存储路径
+file:
+  mac:
+    path: ~/file/
+    avatar: ~/avatar/
+  linux:
+    path: /opt/oying/file/
+    avatar: /opt/oying/avatar/
+  windows:
+    path: C:\oying\file\
+    avatar: C:\oying\avatar\
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5
diff --git a/oying-system/src/main/resources/config/application-prod.yml b/oying-system/src/main/resources/config/application-prod.yml
new file mode 100644
index 0000000..f3cc9c8
--- /dev/null
+++ b/oying-system/src/main/resources/config/application-prod.yml
@@ -0,0 +1,129 @@
+#配置数据源
+spring:
+  datasource:
+    druid:
+      db-type: com.alibaba.druid.pool.DruidDataSource
+      driverClassName: com.mysql.cj.jdbc.Driver
+      url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:oying}?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
+      username: ${DB_USER:root}
+      password: ${DB_PWD:123456}
+      # 初始连接数,建议设置为与最小空闲连接数相同
+      initial-size: 20
+      # 最小空闲连接数,保持足够的空闲连接以应对请求
+      min-idle: 20
+      # 最大连接数,根据并发需求适当增加
+      max-active: 50
+      # 获取连接超时时间(毫秒),调整以满足响应时间要求
+      max-wait: 3000
+      # 启用KeepAlive机制,保持长连接
+      keep-alive: true
+      # 连接有效性检测间隔时间(毫秒),定期检查连接的健康状态
+      time-between-eviction-runs-millis: 60000
+      # 连接在池中最小生存时间(毫秒),确保连接在池中至少存在一段时间
+      min-evictable-idle-time-millis: 300000
+      # 连接在池中最大生存时间(毫秒),防止连接在池中停留过长
+      max-evictable-idle-time-millis: 900000
+      # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除
+      test-while-idle: true
+      # 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个
+      test-on-borrow: true
+      # 是否在归还到池中前进行检验
+      test-on-return: false
+      # 停用 com_ping 探活机制
+      use-ping-method: false
+      # 检测连接是否有效
+      validation-query: SELECT 1
+      # 配置监控统计
+      webStatFilter:
+        enabled: true
+      stat-view-servlet:
+        allow:
+        enabled: true
+        # 控制台管理用户名和密码
+        url-pattern: /druid/*
+        reset-enable: false
+        login-username: admin
+        login-password: 123456
+      filter:
+        stat:
+          enabled: true
+          # 记录慢SQL
+          log-slow-sql: true
+          slow-sql-millis: 1000
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+
+# 登录相关配置
+login:
+  #  是否限制单用户登录
+  single-login: false
+  # Redis用户登录缓存配置
+  user-cache:
+    # 存活时间/秒
+    idle-time: 21600
+  #  验证码
+  code:
+    #  验证码类型配置 查看 LoginProperties 类
+    code-type: SPEC
+    #  登录图形验证码有效时间/分钟
+    expiration: 2
+    #  验证码高度
+    width: 111
+    #  验证码宽度
+    height: 36
+    # 内容长度
+    length: 4
+    # 字体名称,为空则使用默认字体,如遇到线上乱码,设置其他字体即可
+    font-name:
+    # 字体大小
+    font-size: 25
+
+#jwt
+jwt:
+  header: Authorization
+  # 令牌前缀
+  token-start-with: Bearer
+  # 必须使用最少88位的Base64对该令牌进行编码
+  base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
+  # 令牌过期时间 此处单位/毫秒 ,默认2小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html
+  token-validity-in-seconds: 7200000
+  # 在线用户key
+  online-key: "online_token:"
+  # 验证码
+  code-key: "captcha_code:"
+  # token 续期检查时间范围(默认30分钟,单位默认毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期
+  detect: 1800000
+  # 续期时间范围,默认 1小时,这里单位毫秒
+  renew: 3600000
+
+#是否允许生成代码,生产环境设置为false
+generator:
+  enabled: false
+
+#如果生产环境要开启swagger,需要配置请求地址
+#springfox:
+#  documentation:
+#    swagger:
+#      v2:
+#        host: # 接口域名或外网ip
+
+#是否开启 swagger-ui
+swagger:
+  enabled: false
+
+# 文件存储路径
+file:
+  mac:
+    path: ~/file/
+    avatar: ~/avatar/
+  linux:
+    path: /opt/oying/file/
+    avatar: /opt/oying/avatar/
+  windows:
+    path: C:\oying\file\
+    avatar: C:\oying\avatar\
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5
diff --git a/oying-system/src/main/resources/config/application-quartz.yml b/oying-system/src/main/resources/config/application-quartz.yml
new file mode 100644
index 0000000..740462c
--- /dev/null
+++ b/oying-system/src/main/resources/config/application-quartz.yml
@@ -0,0 +1,29 @@
+# 配置 quartz 分布式支持, sql 文件在 sql 目录下,需要导入到数据库,并且需要修改 application.yml 文件的 active: dev 配置
+spring:
+  quartz:
+    # 必需,指定使用 JDBC 存储
+    job-store-type: jdbc
+    properties:
+      org:
+        quartz:
+          scheduler:
+            # 必需,指定调度器实例的名称
+            instanceName: oying
+            # 必需,指定调度器实例的 ID
+            instanceId: auto
+          threadPool:
+            # 可选,线程池的线程数量,可以根据需要调整
+            threadCount: 5
+          jobStore:
+            # 可选,如果你不需要集群,可以去掉
+            isClustered: true
+            # 可选,集群检查间隔时间,可以根据需要调整
+            clusterCheckinInterval: 10000
+            # 必需,指定 JDBC 驱动代理类
+            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+            # 可选,是否使用属性存储,可以根据需要调整
+            useProperties: false
+            # 必需,指定表的前缀
+            tablePrefix: qrtz_
+            # 可选,指定误触发阈值,可以根据需要调整
+            misfireThreshold: 60000
diff --git a/oying-system/src/main/resources/config/application.yml b/oying-system/src/main/resources/config/application.yml
new file mode 100644
index 0000000..d139423
--- /dev/null
+++ b/oying-system/src/main/resources/config/application.yml
@@ -0,0 +1,73 @@
+server:
+  port: 8000
+  http2:
+    # 启用 HTTP/2 支持,提升传输效率
+    enabled: true
+  compression:
+    # 启用 GZIP 压缩,减少传输数据量
+    enabled: true
+    # 需要压缩的 MIME 类型
+    mime-types: text/html, text/xml, text/plain, application/json
+    # 最小压缩响应大小(字节)
+    min-response-size: 1024
+
+mybatis-plus:
+  configuration:
+    # 关闭二级缓存
+    cache-enabled: false
+    # 设置本地缓存作用域
+    local-cache-scope: SESSION
+
+spring:
+  freemarker:
+    check-template-location: false
+  profiles:
+    # 激活的环境,如果需要 quartz 分布式支持,需要修改 active: dev,quartz
+    active: dev
+  data:
+    redis:
+      repositories:
+        enabled: false
+#  pid:
+#    file: /自行指定位置/oying.pid
+
+  redis:
+    #数据库索引
+    database: ${REDIS_DB:1}
+    host: ${REDIS_HOST:127.0.0.1}
+    port: ${REDIS_PORT:6379}
+    password: ${REDIS_PWD:}
+    #连接超时时间
+    timeout: 5000
+    # 连接池配置
+    lettuce:
+      pool:
+        # 连接池最大连接数
+        max-active: 30
+        # 连接池最大阻塞等待时间(毫秒),负值表示没有限制
+        max-wait: -1
+        # 连接池中的最大空闲连接数
+        max-idle: 20
+        # 连接池中的最小空闲连接数
+        min-idle: 1
+
+task:
+  pool:
+    # 核心线程池大小
+    core-pool-size: 10
+    # 最大线程数
+    max-pool-size: 30
+    # 活跃时间
+    keep-alive-seconds: 60
+    # 队列容量
+    queue-capacity: 50
+
+#邮箱验证码有效时间/秒
+code:
+  expiration: 300
+
+#密码加密传输,前端公钥加密,后端私钥解密
+rsa:
+  private_key: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAI1IEx2CBKbywvVmNvfRycty5uysu7ZfeLdOwJst4cACaPhhdSUq5CqdDRMcZeRbOyt8ec09Ztz5qtU6Ve+YwOHuxUPys0IGOSxrXF5U80DnI8ux4k82w8MoCjtB0myF8QsCaCF+uDBhbG1/yQbyhZ3pWrZpH2n9mjaitTtaQVdNAgMBAAECgYA6oLot+JJtpTf6FdyholEXOCtT86pB2ASELQ4IV1XjFBzzVZ4DOnVMqbePQq2VwbYgKZtx7BUPhhu6OGcI8l63v8OTAgoNovCksSA8rSPfCs593JmKFVShsHApkHAH/Klo/PsEV0QvpG9Uf0hTOdNiqbHWAorA/PnuaBr0/anygQJBAPdtuflC9JxPxaySBz2Up7g1QG9xHW459U/2M0Mn/EI+RJdd7vjITeofZ52yIElOmmgjU8XK3edncE1H1YhaLbkCQQCSLQDKcDFHIw0KRm0a51nTknoi23ZloxQEt3/86zJurzheujX3Oo9cY5FvzlvHKWqgAvAiBiivt9hGWnxeNOA1AkEAiHkpPudDbIRDj+/rtnesGtqkc9N8XDPzruspUz1W0mLuCl9xVB+Hej9gM4bwb/6/A/mYV1ySEPTo6HdavB6hYQJAJv8yks9TljLXq8IWIXNPF46gXuRFtd/H22pJDuSAU98THtJ2yzooPPGjPzzCZ2O5Om8OOUWDXT2iyUIio89fcQJAUYS2803tLFouTzaa+SR3kpuqEi6en1yrJNGDe7a4tfGjhQBLmjhCOMuZAjkYYfnN8HiN+dRdKMu/8rpPpC0NKg==
+
+
diff --git a/oying-system/src/main/resources/logback.xml b/oying-system/src/main/resources/logback.xml
new file mode 100644
index 0000000..bba005b
--- /dev/null
+++ b/oying-system/src/main/resources/logback.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="30 seconds" debug="false">
+    <contextName>oying</contextName>
+    <property name="log.charset" value="utf-8" />
+    <property name="log.pattern" value="%contextName- %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}) - %msg%n" />
+
+    <!--输出到控制台-->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+            <charset>${log.charset}</charset>
+        </encoder>
+    </appender>
+
+    <!--普通日志输出到控制台-->
+    <root level="info">
+        <appender-ref ref="console" />
+    </root>
+
+    <!-- Spring 日志级别控制 -->
+    <logger name="org.springframework" level="warn" />
+
+    <!-- DnsServerAddressStreamProviders调整为ERROR -->
+    <logger name="io.netty.resolver.dns.DnsServerAddressStreamProviders" level="ERROR"/>
+
+    <!-- 设置其他类的日志级别为 ERROR -->
+    <logger name="org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/]" level="ERROR"/>
+    <logger name="org.springframework.web.servlet.DispatcherServlet" level="ERROR"/>
+</configuration>
diff --git a/oying-system/src/main/resources/mapper/quartz/QuartzJobMapper.xml b/oying-system/src/main/resources/mapper/quartz/QuartzJobMapper.xml
new file mode 100644
index 0000000..5a6bcb7
--- /dev/null
+++ b/oying-system/src/main/resources/mapper/quartz/QuartzJobMapper.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.modules.quartz.mapper.QuartzJobMapper">
+
+    <resultMap id="BaseResultMap" type="com.oying.modules.quartz.domain.QuartzJob">
+        <id column="job_id" property="id" jdbcType="BIGINT"/>
+        <result column="job_name" property="jobName" jdbcType="VARCHAR"/>
+        <result column="bean_name" property="beanName" jdbcType="VARCHAR"/>
+        <result column="method_name" property="methodName" jdbcType="VARCHAR"/>
+        <result column="params" property="params" jdbcType="VARCHAR"/>
+        <result column="cron_expression" property="cronExpression" jdbcType="VARCHAR"/>
+        <result column="is_pause" property="isPause" jdbcType="TINYINT"/>
+        <result column="person_in_charge" property="personInCharge" jdbcType="VARCHAR"/>
+        <result column="email" property="email" jdbcType="VARCHAR"/>
+        <result column="sub_task" property="subTask" jdbcType="VARCHAR"/>
+        <result column="pause_after_failure" property="pauseAfterFailure" jdbcType="TINYINT"/>
+        <result column="description" property="description" jdbcType="VARCHAR"/>
+        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
+        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
+        <result column="create_by" property="createBy" jdbcType="VARCHAR"/>
+        <result column="update_by" property="updateBy" jdbcType="VARCHAR"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        job_id, job_name, bean_name, method_name, params, cron_expression, is_pause, person_in_charge, email, sub_task, pause_after_failure, description, create_time, update_time, create_by, update_by
+    </sql>
+
+    <select id="findAll" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List"/>
+        FROM sys_quartz_job
+        <where>
+            <if test="criteria.jobName != null and criteria.jobName != ''">
+                AND job_name LIKE CONCAT('%',#{criteria.jobName},'%')
+            </if>
+            <if test="criteria.createTime != null and criteria.createTime.size() > 0">
+                AND update_time BETWEEN #{criteria.createTime[0]} AND #{criteria.createTime[1]}
+            </if>
+        </where>
+        ORDER BY job_id DESC
+    </select>
+
+    <select id="findByIsPauseIsFalse" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List"/>
+        FROM sys_quartz_job
+        WHERE is_pause = 0
+    </select>
+</mapper>
diff --git a/oying-system/src/main/resources/mapper/quartz/QuartzLogMapper.xml b/oying-system/src/main/resources/mapper/quartz/QuartzLogMapper.xml
new file mode 100644
index 0000000..a7c0127
--- /dev/null
+++ b/oying-system/src/main/resources/mapper/quartz/QuartzLogMapper.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.modules.quartz.mapper.QuartzLogMapper">
+
+    <resultMap id="BaseResultMap" type="com.oying.modules.quartz.domain.QuartzLog">
+        <id column="log_id" property="id" jdbcType="BIGINT"/>
+        <result column="job_name" property="jobName" jdbcType="VARCHAR"/>
+        <result column="bean_name" property="beanName" jdbcType="VARCHAR"/>
+        <result column="method_name" property="methodName" jdbcType="VARCHAR"/>
+        <result column="params" property="params" jdbcType="VARCHAR"/>
+        <result column="cron_expression" property="cronExpression" jdbcType="VARCHAR"/>
+        <result column="is_success" property="isSuccess" jdbcType="VARCHAR"/>
+        <result column="exception_detail" property="exceptionDetail" jdbcType="BIGINT"/>
+        <result column="time" property="time" jdbcType="BIGINT"/>
+        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        log_id, job_name, bean_name, method_name, params, cron_expression, is_success, exception_detail, time, create_time
+    </sql>
+
+    <select id="findAll" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List"/>
+        FROM sys_quartz_log
+        <where>
+            <if test="criteria.jobName != null and criteria.jobName != ''">
+                AND job_name LIKE CONCAT('%',#{criteria.jobName},'%')
+            </if>
+            <if test="criteria.isSuccess != null">
+                AND is_success = #{criteria.isSuccess}
+            </if>
+            <if test="criteria.createTime != null and criteria.createTime.size() > 0">
+                AND create_time BETWEEN #{criteria.createTime[0]} AND #{criteria.createTime[1]}
+            </if>
+        </where>
+        ORDER BY log_id DESC
+    </select>
+
+</mapper>
diff --git a/oying-system/src/main/resources/mapper/system/DeptMapper.xml b/oying-system/src/main/resources/mapper/system/DeptMapper.xml
new file mode 100644
index 0000000..10be3a2
--- /dev/null
+++ b/oying-system/src/main/resources/mapper/system/DeptMapper.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.modules.system.mapper.DeptMapper">
+    <resultMap id="BaseResultMap" type="com.oying.modules.system.domain.Dept">
+        <id column="dept_id" property="id"/>
+        <result column="dept_sort" property="deptSort"/>
+        <result column="name" property="name"/>
+        <result column="enabled" property="enabled"/>
+        <result column="pid" property="pid"/>
+        <result column="sub_count" property="subCount"/>
+        <result column="create_by" property="createBy"/>
+        <result column="update_by" property="updateBy"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_time" property="updateTime"/>
+    </resultMap>
+
+    <sql id="BaseResultMap">
+        dept_id, name, pid, sub_count, create_time, update_time, create_by, update_by, enabled, dept_sort
+    </sql>
+
+    <select id="findAll" resultMap="BaseResultMap">
+        select
+        <include refid="BaseResultMap"/>
+        from sys_dept
+        <where>
+            <if test="criteria.ids != null and criteria.ids.size() > 0">
+                and dept_id in
+                <foreach collection="criteria.ids" item="id" open="(" separator="," close=")">
+                    #{id}
+                </foreach>
+            </if>
+            <if test="criteria.name != null and criteria.name != ''">
+                and name like concat('%', #{criteria.name}, '%')
+            </if>
+            <if test="criteria.enabled != null">
+                and enabled = #{criteria.enabled}
+            </if>
+            <if test="criteria.pid != null">
+                and pid = #{criteria.pid}
+            </if>
+            <if test="criteria.pidIsNull != null">
+                and pid is null
+            </if>
+            <if test="criteria.createTime != null and criteria.createTime.size() != 0">
+                and create_time between #{criteria.createTime[0]} and #{criteria.createTime[1]}
+            </if>
+        </where>
+        order by dept_sort
+    </select>
+
+    <select id="findByPid" resultMap="BaseResultMap">
+        select
+        <include refid="BaseResultMap"/>
+        from sys_dept
+        where pid = #{pid}
+    </select>
+
+    <select id="findByPidIsNull" resultMap="BaseResultMap">
+        select
+        <include refid="BaseResultMap"/>
+        from sys_dept
+        where pid is null
+    </select>
+
+    <select id="findByRoleId" resultType="com.oying.modules.system.domain.Dept">
+        select d.dept_id as id, d.name, d.pid, d.sub_count, d.create_time, d.update_time, d.create_by, d.update_by, d.enabled, d.dept_sort
+        from sys_dept d, sys_roles_depts r
+        where d.dept_id = r.dept_id and r.role_id = #{roleId}
+    </select>
+</mapper>
diff --git a/oying-system/src/main/resources/mapper/system/DictDetailMapper.xml b/oying-system/src/main/resources/mapper/system/DictDetailMapper.xml
new file mode 100644
index 0000000..ca3d153
--- /dev/null
+++ b/oying-system/src/main/resources/mapper/system/DictDetailMapper.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.modules.system.mapper.DictDetailMapper">
+<resultMap id="BaseResultMap" type="com.oying.modules.system.domain.DictDetail">
+        <id column="detail_id" property="id" jdbcType="BIGINT"/>
+        <result column="label" property="label" jdbcType="VARCHAR"/>
+        <result column="value" property="value" jdbcType="VARCHAR"/>
+        <result column="dict_sort" property="dictSort" jdbcType="INTEGER"/>
+        <result column="create_by" property="createBy" jdbcType="VARCHAR"/>
+        <result column="update_by" property="updateBy" jdbcType="VARCHAR"/>
+        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
+        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
+        <association property="dict" javaType="com.oying.modules.system.domain.Dict">
+            <id column="dict_id" property="id" jdbcType="BIGINT"/>
+            <result column="name" property="name" jdbcType="VARCHAR"/>
+        </association>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        dd.detail_id, dd.label, dd.`value`, dd.`dict_sort`, dd.create_by, dd.update_by, dd.create_time, dd.update_time, d.dict_id, d.name
+    </sql>
+
+    <select id="findAll" resultMap="BaseResultMap">
+        select <include refid="Base_Column_List"/>
+        from sys_dict_detail dd, sys_dict d
+        where dd.dict_id = d.dict_id
+        <if test="criteria.dictName != null and criteria.dictName != ''">
+            and d.name = #{criteria.dictName}
+        </if>
+        <if test="criteria.label != null and criteria.label != ''">
+            and dd.label = #{criteria.label}
+        </if>
+        order by dd.dict_sort, dd.dict_id desc
+    </select>
+
+    <select id="findByDictName" resultMap="BaseResultMap">
+        select <include refid="Base_Column_List"/>
+        from sys_dict_detail dd, sys_dict d
+        where dd.dict_id = d.dict_id
+        <if test="name != null and name != ''">
+            and d.name = #{name}
+        </if>
+        order by dd.dict_sort
+    </select>
+
+    <delete id="deleteByDictBatchIds">
+        delete from sys_dict_detail
+        where dict_id in
+        <foreach collection="dictIds" item="item" index="index" open="(" separator="," close=")">
+            #{item}
+        </foreach>
+    </delete>
+</mapper>
diff --git a/oying-system/src/main/resources/mapper/system/DictMapper.xml b/oying-system/src/main/resources/mapper/system/DictMapper.xml
new file mode 100644
index 0000000..355f588
--- /dev/null
+++ b/oying-system/src/main/resources/mapper/system/DictMapper.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.modules.system.mapper.DictMapper">
+    <resultMap id="BaseResultMap" type="com.oying.modules.system.domain.Dict">
+        <id column="dict_id" property="id" jdbcType="BIGINT"/>
+        <result column="name" property="name" jdbcType="VARCHAR"/>
+        <result column="description" property="description" jdbcType="VARCHAR"/>
+        <result column="create_by" property="createBy" jdbcType="VARCHAR"/>
+        <result column="update_by" property="updateBy" jdbcType="VARCHAR"/>
+        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
+        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        d.dict_id, d.name, d.description, d.create_by, d.update_by, d.create_time, d.update_time
+    </sql>
+
+    <select id="findAll" resultMap="BaseResultMap">
+        SELECT <include refid="Base_Column_List"/>
+        from sys_dict d
+        <where>
+            <if test="criteria.blurry != null and criteria.blurry != ''">
+                and (
+                d.name like concat('%', #{criteria.blurry}, '%')
+                or d.description like concat('%', #{criteria.blurry}, '%')
+                )
+            </if>
+        </where>
+        order by d.dict_id desc
+    </select>
+</mapper>
diff --git a/oying-system/src/main/resources/mapper/system/JobMapper.xml b/oying-system/src/main/resources/mapper/system/JobMapper.xml
new file mode 100644
index 0000000..d0b210d
--- /dev/null
+++ b/oying-system/src/main/resources/mapper/system/JobMapper.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.modules.system.mapper.JobMapper">
+    <resultMap id="BaseResultMap" type="com.oying.modules.system.domain.Job">
+        <id column="job_id" property="id"/>
+        <result column="name" property="name"/>
+        <result column="job_sort" property="jobSort"/>
+        <result column="enabled" property="enabled"/>
+        <result column="create_by" property="createBy"/>
+        <result column="update_by" property="updateBy"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_time" property="updateTime"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        job_id, name, job_sort, enabled, create_by, update_by, create_time, update_time
+    </sql>
+
+    <select id="findAll" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List"/>
+        from sys_job
+        <where>
+            <if test="criteria.name != null and criteria.name != ''">
+                and name like concat('%', #{criteria.name}, '%')
+            </if>
+            <if test="criteria.enabled != null">
+                and enabled = #{criteria.enabled}
+            </if>
+            <if test="criteria.createTime != null and criteria.createTime.size() != 0">
+                and create_time between #{criteria.createTime[0]} and #{criteria.createTime[1]}
+            </if>
+        </where>
+        order by job_sort, job_id desc
+    </select>
+</mapper>
diff --git a/oying-system/src/main/resources/mapper/system/MenuMapper.xml b/oying-system/src/main/resources/mapper/system/MenuMapper.xml
new file mode 100644
index 0000000..65993c1
--- /dev/null
+++ b/oying-system/src/main/resources/mapper/system/MenuMapper.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.modules.system.mapper.MenuMapper">
+    <resultMap id="BaseResultMap" type="com.oying.modules.system.domain.Menu">
+        <id column="menu_id" property="id"/>
+        <result column="title" property="title"/>
+        <result column="name" property="componentName"/>
+        <result column="menu_sort" property="menuSort"/>
+        <result column="component" property="component"/>
+        <result column="path" property="path"/>
+        <result column="type" property="type"/>
+        <result column="permission" property="permission"/>
+        <result column="icon" property="icon"/>
+        <result column="cache" property="cache"/>
+        <result column="hidden" property="hidden"/>
+        <result column="pid" property="pid"/>
+        <result column="sub_count" property="subCount"/>
+        <result column="i_frame" property="iFrame"/>
+        <result column="create_by" property="createBy"/>
+        <result column="update_by" property="updateBy"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_time" property="updateTime"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        menu_id, title, name, menu_sort, component, path, type, permission, icon, cache, hidden, pid, sub_count, i_frame, create_by, update_by, create_time, update_time
+    </sql>
+
+    <select id="findAll" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List"/>
+        from sys_menu
+        <where>
+            <if test="criteria.blurry != null and criteria.blurry != ''">
+                and (
+                    title like concat('%',#{criteria.blurry},'%')
+                    or name like concat('%',#{criteria.blurry},'%')
+                    or permission like concat('%',#{criteria.blurry},'%')
+                )
+            </if>
+            <if test="criteria.pidIsNull != null">
+                and pid is null
+            </if>
+            <if test="criteria.pid != null">
+                and pid = #{criteria.pid}
+            </if>
+            <if test="criteria.createTime != null and criteria.createTime.size() != 0">
+                and create_time between #{criteria.createTime[0]} and #{criteria.createTime[1]}
+            </if>
+        </where>
+        order by menu_sort
+    </select>
+
+    <select id="findByRoleIdsAndTypeNot" resultMap="BaseResultMap">
+        SELECT m.* FROM sys_menu m, sys_roles_menus r
+        WHERE m.menu_id = r.menu_id AND r.role_id IN
+        <foreach collection="roleIds" item="roleId" open="(" separator="," close=")">
+            #{roleId}
+        </foreach>
+        AND type != #{type}
+        order by m.menu_sort
+    </select>
+
+    <select id="findByPidOrderByMenuSort" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List"/>
+        from sys_menu
+        WHERE pid = #{pid}
+        ORDER BY menu_sort
+    </select>
+
+    <select id="findByPidIsNullOrderByMenuSort" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List"/>
+        from sys_menu
+        where pid is null
+        order by menu_sort
+    </select>
+</mapper>
diff --git a/oying-system/src/main/resources/mapper/system/RoleDeptMapper.xml b/oying-system/src/main/resources/mapper/system/RoleDeptMapper.xml
new file mode 100644
index 0000000..664c04d
--- /dev/null
+++ b/oying-system/src/main/resources/mapper/system/RoleDeptMapper.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.modules.system.mapper.RoleDeptMapper">
+
+    <insert id="insertData">
+        insert into sys_roles_depts (role_id, dept_id)
+        values
+        <foreach collection="depts" item="item" open="(" separator="),(" close=")">
+            #{roleId}, #{item.id}
+        </foreach>
+    </insert>
+
+    <delete id="deleteByRoleId">
+        delete from sys_roles_depts
+        where role_id = #{roleId}
+    </delete>
+
+    <delete id="deleteByRoleIds">
+        delete from sys_roles_depts
+        where role_id in
+        <foreach collection="roleIds" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>
diff --git a/oying-system/src/main/resources/mapper/system/RoleMapper.xml b/oying-system/src/main/resources/mapper/system/RoleMapper.xml
new file mode 100644
index 0000000..3134824
--- /dev/null
+++ b/oying-system/src/main/resources/mapper/system/RoleMapper.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.modules.system.mapper.RoleMapper">
+    <resultMap id="BaseResultMap" type="com.oying.modules.system.domain.Role">
+        <id column="role_role_id" property="id"/>
+        <result column="role_name" property="name"/>
+        <result column="role_data_scope" property="dataScope"/>
+        <result column="role_level" property="level"/>
+        <result column="role_description" property="description"/>
+        <result column="role_create_by" property="createBy"/>
+        <result column="role_update_by" property="updateBy"/>
+        <result column="role_create_time" property="createTime"/>
+        <result column="role_update_time" property="updateTime"/>
+        <collection property="menus" ofType="com.oying.modules.system.domain.Menu">
+            <id column="menu_id" property="id"/>
+            <result column="menu_title" property="title"/>
+            <result column="menu_permission" property="permission"/>
+        </collection>
+        <collection property="depts" ofType="com.oying.modules.system.domain.Dept">
+            <id column="dept_id" property="id"/>
+            <result column="dept_name" property="name"/>
+        </collection>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        role.role_id as role_role_id, role.name as role_name, role.data_scope as role_data_scope,
+        role.level as role_level, role.description as role_description, role.create_by as role_create_by,
+        role.update_by as role_update_by, role.create_time as role_create_time, role.update_time as role_update_time
+    </sql>
+
+    <sql id="Menu_Column_List">
+        menu.menu_id as menu_id, menu.title as menu_title, menu.permission as menu_permission
+    </sql>
+
+    <sql id="Dept_Column_List">
+        dept.dept_id as dept_id, dept.name as dept_name
+    </sql>
+
+    <sql id="Where_sql">
+        <where>
+            <if test="criteria.blurry != null and criteria.blurry != ''">
+                and (
+                role.name like concat('%', #{criteria.blurry}, '%')
+                or role.description like concat('%', #{criteria.blurry}, '%')
+                )
+            </if>
+            <if test="criteria.createTime != null and criteria.createTime.size() != 0">
+                and role.create_time between #{criteria.createTime[0]} and #{criteria.createTime[1]}
+            </if>
+        </where>
+    </sql>
+
+    <select id="queryAll" resultType="com.oying.modules.system.domain.Role">
+        select role_id as id, name, level
+        from sys_role order by level
+    </select>
+
+    <select id="findAll" resultMap="BaseResultMap">
+        select role.*,
+        <include refid="Dept_Column_List"/>,
+        <include refid="Menu_Column_List"/>
+        from (
+        select
+        <include refid="Base_Column_List"/>
+        from sys_role role
+        <include refid="Where_sql"/>
+        order by role.level
+        <if test="criteria.offset != null">
+            limit #{criteria.offset}, #{criteria.size}
+        </if>
+        ) role
+        left join sys_roles_menus srm on role.role_role_id = srm.role_id
+        left join  sys_menu menu on menu.menu_id = srm.menu_id
+        left join sys_roles_depts srd on role.role_role_id = srd.role_id
+        left join sys_dept dept on dept.dept_id = srd.dept_id
+        order by role.role_level
+    </select>
+
+    <select id="countAll" resultType="java.lang.Long">
+        select count(*)
+        from sys_role role
+        <include refid="Where_sql"/>
+    </select>
+
+    <select id="findByName" resultType="com.oying.modules.system.domain.Role">
+        select role_id as id from sys_role
+        where name = #{name}
+    </select>
+
+    <select id="findById" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List"/>,
+        <include refid="Dept_Column_List"/>,
+        <include refid="Menu_Column_List"/>
+        from sys_role role
+        left join sys_roles_menus srm on role.role_id = srm.role_id
+        left join sys_menu menu on menu.menu_id = srm.menu_id
+        left join sys_roles_depts srd on role.role_id = srd.role_id
+        left join sys_dept dept on dept.dept_id = srd.dept_id
+        where role.role_id = #{roleId}
+    </select>
+
+    <select id="findByUserId" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List"/>,
+        <include refid="Dept_Column_List"/>,
+        <include refid="Menu_Column_List"/>
+        from sys_role role
+        left join sys_roles_menus srm on role.role_id = srm.role_id
+        left join sys_menu menu on menu.menu_id = srm.menu_id
+        left join sys_roles_depts srd on role.role_id = srd.role_id
+        left join sys_dept dept on dept.dept_id = srd.dept_id
+        left join sys_users_roles ur on role.role_id = ur.role_id
+        WHERE role.role_id = ur.role_id AND ur.user_id = #{userId}
+    </select>
+
+    <select id="countByDepts" resultType="int">
+        select count(*)
+        from sys_role r, sys_roles_depts d
+        where r.role_id = d.role_id
+        and d.dept_id in
+        <foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
+            #{deptId}
+        </foreach>
+    </select>
+</mapper>
diff --git a/oying-system/src/main/resources/mapper/system/RoleMenuMapper.xml b/oying-system/src/main/resources/mapper/system/RoleMenuMapper.xml
new file mode 100644
index 0000000..dd88a0f
--- /dev/null
+++ b/oying-system/src/main/resources/mapper/system/RoleMenuMapper.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.modules.system.mapper.RoleMenuMapper">
+
+    <insert id="insertData">
+        insert into sys_roles_menus (role_id, menu_id)
+        values
+        <foreach collection="menus" item="item" open="(" separator="),(" close=")">
+            #{roleId}, #{item.id}
+        </foreach>
+    </insert>
+
+    <delete id="deleteByRoleId">
+        delete from sys_roles_menus
+        where role_id = #{roleId}
+    </delete>
+
+    <delete id="deleteByRoleIds">
+        delete from sys_roles_menus
+        where role_id in
+        <foreach collection="roleIds" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <delete id="deleteByMenuId">
+        delete from sys_roles_menus
+        where menu_id = #{menuId}
+    </delete>
+</mapper>
diff --git a/oying-system/src/main/resources/mapper/system/UserJobMapper.xml b/oying-system/src/main/resources/mapper/system/UserJobMapper.xml
new file mode 100644
index 0000000..43efef1
--- /dev/null
+++ b/oying-system/src/main/resources/mapper/system/UserJobMapper.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.modules.system.mapper.UserJobMapper">
+
+    <insert id="insertData">
+        insert into sys_users_jobs (user_id, job_id)
+        values
+        <foreach collection="jobs" item="item" open="(" separator="),(" close=")">
+            #{userId}, #{item.id}
+        </foreach>
+    </insert>
+
+    <delete id="deleteByUserId">
+        delete from sys_users_jobs
+        where user_id = #{userId}
+    </delete>
+
+    <delete id="deleteByUserIds">
+        delete from sys_users_jobs
+        where user_id in
+        <foreach collection="userIds" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>
diff --git a/oying-system/src/main/resources/mapper/system/UserMapper.xml b/oying-system/src/main/resources/mapper/system/UserMapper.xml
new file mode 100644
index 0000000..a71e0a3
--- /dev/null
+++ b/oying-system/src/main/resources/mapper/system/UserMapper.xml
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.modules.system.mapper.UserMapper">
+    <resultMap id="BaseResultMap" type="com.oying.modules.system.domain.User">
+        <id column="user_user_id" property="id"/>
+        <result column="user_dept_id" property="deptId"/>
+        <result column="user_username" property="username"/>
+        <result column="user_nick_name" property="nickName"/>
+        <result column="user_email" property="email"/>
+        <result column="user_phone" property="phone"/>
+        <result column="user_gender" property="gender"/>
+        <result column="user_avatar_name" property="avatarName"/>
+        <result column="user_avatar_path" property="avatarPath"/>
+        <result column="user_password" property="password"/>
+        <result column="user_is_admin" property="isAdmin"/>
+        <result column="user_enabled" property="enabled"/>
+        <result column="user_pwd_reset_time" property="pwdResetTime"/>
+        <result column="user_create_by" property="createBy"/>
+        <result column="user_update_by" property="updateBy"/>
+        <result column="user_create_time" property="createTime"/>
+        <result column="user_update_time" property="updateTime"/>
+        <association property="dept" javaType="com.oying.modules.system.domain.Dept">
+            <id column="dept_id" property="id"/>
+            <result column="dept_name" property="name"/>
+        </association>
+        <collection property="jobs" ofType="com.oying.modules.system.domain.Job">
+            <id column="job_id" property="id"/>
+            <result column="job_name" property="name"/>
+        </collection>
+        <collection property="roles" ofType="com.oying.modules.system.domain.Role">
+            <id column="role_id" property="id"/>
+            <result column="role_name" property="name"/>
+            <result column="role_level" property="level"/>
+            <result column="role_data_scope" property="dataScope"/>
+        </collection>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        u.user_id as user_user_id, u.dept_id as user_dept_id, u.username as user_username,
+           u.nick_name as user_nick_name, u.email as user_email, u.phone as user_phone,
+           u.gender as user_gender, u.avatar_name as user_avatar_name, u.avatar_path as user_avatar_path,
+           u.enabled as user_enabled, u.pwd_reset_time as user_pwd_reset_time, u.create_by as user_create_by,
+           u.update_by as user_update_by, u.create_time as user_create_time, u.update_time as user_update_time,
+           d.dept_id as dept_id, d.name as dept_name
+    </sql>
+
+    <sql id="Job_Column_List">
+        j.job_id as job_id, j.name as job_name
+    </sql>
+
+    <sql id="Role_Column_List">
+        r.role_id as role_id, r.name as role_name, r.level as role_level, r.data_scope as role_data_scope
+    </sql>
+
+    <sql id="Whrer_Sql">
+        <where>
+            <if test="criteria.id != null">
+                and u.user_id = #{criteria.id}
+            </if>
+            <if test="criteria.enabled != null">
+                and u.enabled = #{criteria.enabled}
+            </if>
+            <if test="criteria.deptIds != null and criteria.deptIds.size() != 0">
+                and u.dept_id in
+                <foreach collection="criteria.deptIds" item="deptId" open="(" separator="," close=")">
+                    #{deptId}
+                </foreach>
+            </if>
+            <if test="criteria.blurry != null and criteria.blurry != ''">
+                and (
+                u.username like concat('%', #{criteria.blurry}, '%')
+                or u.nick_name like concat('%', #{criteria.blurry}, '%')
+                or u.email like concat('%', #{criteria.blurry}, '%')
+                )
+            </if>
+            <if test="criteria.createTime != null and criteria.createTime.size() != 0">
+                and u.create_time between #{criteria.createTime[0]} and #{criteria.createTime[1]}
+            </if>
+        </where>
+    </sql>
+
+    <select id="findAll" resultMap="BaseResultMap">
+        select u.*,
+        <include refid="Job_Column_List"/>,
+        <include refid="Role_Column_List"/>
+        from (
+        select
+        <include refid="Base_Column_List"/>
+        from sys_user u
+        left join sys_dept d on u.dept_id = d.dept_id
+        <include refid="Whrer_Sql"/>
+        order by u.user_id desc
+        <if test="criteria.offset != null">
+            limit #{criteria.offset}, #{criteria.size}
+        </if>
+        ) u
+        left join sys_users_jobs suj on u.user_user_id = suj.user_id
+        left join sys_job j on suj.job_id = j.job_id
+        left join sys_users_roles sur on u.user_user_id = sur.user_id
+        left join sys_role r on sur.role_id = r.role_id
+        order by u.user_user_id desc
+    </select>
+
+    <select id="countAll" resultType="java.lang.Long">
+        select count(*)
+        from sys_user u
+        <include refid="Whrer_Sql"/>
+    </select>
+
+    <select id="findByUsername" resultMap="BaseResultMap">
+        select
+        u.password user_password, u.is_admin user_is_admin,
+        <include refid="Base_Column_List"/>
+        from sys_user u
+        left join sys_dept d on u.dept_id = d.dept_id
+        where u.username = #{username}
+    </select>
+
+    <select id="findByEmail" resultType="com.oying.modules.system.domain.User">
+        select user_id as id, username from sys_user
+        where email = #{email}
+    </select>
+
+    <select id="findByPhone" resultType="com.oying.modules.system.domain.User">
+        select user_id as id, username from sys_user
+        where phone = #{phone}
+    </select>
+
+    <select id="findByRoleId" resultType="com.oying.modules.system.domain.User">
+        SELECT u.user_id as id, u.username FROM sys_user u, sys_users_roles r
+        WHERE u.user_id = r.user_id AND r.role_id = #{roleId}
+        group by u.user_id
+    </select>
+
+    <select id="findByRoleDeptId" resultType="com.oying.modules.system.domain.User">
+        SELECT u.* FROM sys_user u, sys_users_roles r, sys_roles_depts d
+        WHERE u.user_id = r.user_id AND r.role_id = d.role_id AND d.dept_id = #{deptId}
+        group by u.user_id
+    </select>
+
+    <select id="findByMenuId" resultType="com.oying.modules.system.domain.User">
+        SELECT u.user_id as id, u.username FROM sys_user u, sys_users_roles ur, sys_roles_menus rm
+        WHERE u.user_id = ur.user_id AND ur.role_id = rm.role_id AND rm.menu_id = #{menuId}
+        group by u.user_id
+    </select>
+
+    <select id="countByJobs" resultType="int">
+        SELECT count(*) FROM sys_user u, sys_users_jobs j
+        WHERE u.user_id = j.user_id AND j.job_id IN
+        <foreach collection="jobIds" item="jobId" open="(" separator="," close=")">
+            #{jobId}
+        </foreach>
+    </select>
+
+    <select id="countByDepts" resultType="int">
+        SELECT count(*) FROM sys_user u
+        WHERE u.dept_id IN
+        <foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
+            #{deptId}
+        </foreach>
+    </select>
+
+    <select id="countByRoles" resultType="int">
+        SELECT count(*) FROM sys_user u, sys_users_roles r
+        WHERE u.user_id = r.user_id AND r.role_id in
+        <foreach collection="roleIds" item="roleId" open="(" separator="," close=")">
+            #{roleId}
+        </foreach>
+    </select>
+
+    <update id="resetPwd">
+        update sys_user set password = #{pwd}
+        where user_id in
+        <foreach collection="userIds" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </update>
+</mapper>
diff --git a/oying-system/src/main/resources/mapper/system/UserRoleMapper.xml b/oying-system/src/main/resources/mapper/system/UserRoleMapper.xml
new file mode 100644
index 0000000..8ec4537
--- /dev/null
+++ b/oying-system/src/main/resources/mapper/system/UserRoleMapper.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.modules.system.mapper.UserRoleMapper">
+
+    <insert id="insertData">
+        insert into sys_users_roles (user_id, role_id)
+        values
+        <foreach collection="roles" item="item" open="(" separator="),(" close=")">
+            #{userId}, #{item.id}
+        </foreach>
+    </insert>
+
+    <delete id="deleteByUserId">
+        delete from sys_users_roles
+        where user_id = #{userId}
+    </delete>
+
+    <delete id="deleteByUserIds">
+        delete from sys_users_roles
+        where user_id in
+        <foreach collection="userIds" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>
diff --git a/oying-system/src/main/resources/spy.properties b/oying-system/src/main/resources/spy.properties
new file mode 100644
index 0000000..1c967b0
--- /dev/null
+++ b/oying-system/src/main/resources/spy.properties
@@ -0,0 +1,20 @@
+# 应用的拦截模块
+modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
+# 自定义日志打印
+logMessageFormat=com.oying.config.mybatis.CustomP6SpyLogger
+# 日志输出到控制台
+appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
+# 日期格式
+dateformat=yyyy-MM-dd HH:mm:ss
+# 实际驱动 可多个
+driverlist=com.mysql.cj.jdbc.Driver
+# 是否开启慢SQL记录
+outagedetection=true
+# 慢SQL记录标准 2 秒
+outagedetectioninterval=2
+# 是否过滤 Log
+filter=true
+# 过滤 Log 时所排除的 sql 关键字,以逗号分隔
+exclude=SELECT 1,INSERT INTO sys_log
+# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
+excludecategories=info,debug,result,commit,resultset
diff --git a/oying-system/src/main/resources/template/email.ftl b/oying-system/src/main/resources/template/email.ftl
new file mode 100644
index 0000000..0d61630
--- /dev/null
+++ b/oying-system/src/main/resources/template/email.ftl
@@ -0,0 +1,48 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <style>
+        @page {
+            margin: 0;
+        }
+    </style>
+</head>
+<body style="margin: 0px;
+            padding: 0px;
+			font: 100% SimSun, Microsoft YaHei, Times New Roman, Verdana, Arial, Helvetica, sans-serif;
+            color: #000;">
+<div style="height: auto;
+			width: 820px;
+			min-width: 820px;
+			margin: 0 auto;
+			margin-top: 20px;
+            border: 1px solid #eee;">
+    <div style="padding: 10px;padding-bottom: 0px;">
+        <p style="margin-bottom: 10px;padding-bottom: 0px;">尊敬的用户,您好:</p>
+        <p style="text-indent: 2em; margin-bottom: 10px;">您正在申请邮箱验证,您的验证码为:</p>
+        <p style="text-align: center;
+			font-family: Times New Roman;
+			font-size: 22px;
+			color: #C60024;
+			padding: 20px 0px;
+			margin-bottom: 10px;
+			font-weight: bold;
+			background: #ebebeb;">${code}</p>
+        <div class="foot-hr hr" style="margin: 0 auto;
+			z-index: 111;
+			width: 800px;
+			margin-top: 30px;
+			border-top: 1px solid #DA251D;">
+        </div>
+        <div style="text-align: center;
+			font-size: 12px;
+			padding: 20px 0px;
+			font-family: Microsoft YaHei;">
+            Copyright &copy;${.now?string("yyyy")} <a hover="color: #DA251D;" style="color: #999;" href="https://127.0.0.1/elunez/oying" target="_blank">OYING</a> 后台管理系统 All Rights Reserved.
+        </div>
+
+    </div>
+</div>
+</body>
+</html>
diff --git a/oying-system/src/main/resources/template/taskAlarm.ftl b/oying-system/src/main/resources/template/taskAlarm.ftl
new file mode 100644
index 0000000..1419532
--- /dev/null
+++ b/oying-system/src/main/resources/template/taskAlarm.ftl
@@ -0,0 +1,69 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <style>
+        @page {
+            margin: 0;
+        }
+    </style>
+</head>
+<body style="margin: 0px;
+            padding: 0px;
+			font: 100% SimSun, Microsoft YaHei, Times New Roman, Verdana, Arial, Helvetica, sans-serif;
+            color: #000;">
+<div style="height: auto;
+			margin: 0 auto;
+			margin-top: 20px;
+			padding: 20px;
+            border: 1px solid #eee;">
+        <div>
+            <p style="margin-bottom: 10px;">任务信息:</p>
+            <table style="border-collapse: collapse;">
+                <tr>
+                    <th style="padding: .65em;background: #666;border: 1px solid #777;color: #fff;">任务名称</th>
+                    <th style="padding: .65em;background: #666;border: 1px solid #777;color: #fff;">Bean名称</th>
+                    <th style="padding: .65em;background: #666;border: 1px solid #777;color: #fff;">执行方法</th>
+                    <th style="padding: .65em;background: #666;border: 1px solid #777;color: #fff;">参数内容</th>
+                    <th style="padding: .65em;background: #666;border: 1px solid #777;color: #fff;">Cron表达式</th>
+                    <th style="padding: .65em;background: #666;border: 1px solid #777;color: #fff;">描述内容</th>
+                </tr>
+                <tr>
+                    <td style="padding: .65em;border: 1px solid#777;">${task.jobName}</td>
+                    <td style="padding: .65em;border: 1px solid#777;">${task.beanName}</td>
+                    <td style="padding: .65em;border: 1px solid#777;">${task.methodName}</td>
+                    <td style="padding: .65em;border: 1px solid#777;">${(task.params)!""}</td>
+                    <td style="padding: .65em;border: 1px solid#777;">${task.cronExpression}</td>
+                    <td style="padding: .65em;border: 1px solid#777;">${(task.description)!""}</td>
+                </tr>
+            </table>
+        </div>
+        <div>
+            <p style="margin-bottom: 10px;">异常信息:</p>
+            <pre style="position: relative;
+  padding: 15px;
+  line-height: 20px;
+  border-left: 5px solid #ddd;
+  color: #333;
+  font-family: Courier New, serif;
+  font-size: 12px">
+                ${msg}
+            </pre>
+        </div>
+        <div class="foot-hr hr" style="margin: 0 auto;
+			z-index: 111;
+			width: 800px;
+			margin-top: 30px;
+			border-top: 1px solid #DA251D;">
+        </div>
+        <div style="text-align: center;
+			font-size: 12px;
+			padding: 20px 0px;
+			font-family: Microsoft YaHei;">
+            Copyright &copy;${.now?string("yyyy")} <a hover="color: #DA251D;" style="color: #999;" href="https://github.com/elunez/oying" target="_blank">OYING</a> 后台管理系统 All Rights Reserved.
+        </div>
+
+    </div>
+</div>
+</body>
+</html>
diff --git a/oying-tools/pom.xml b/oying-tools/pom.xml
new file mode 100644
index 0000000..fce1b0f
--- /dev/null
+++ b/oying-tools/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>oying</artifactId>
+        <groupId>com.oying</groupId>
+        <version>1.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>oying-tools</artifactId>
+    <name>工具模块</name>
+
+    <properties>
+        <mail.version>1.4.7</mail.version>
+    </properties>
+
+    <dependencies>
+        <!-- 同时需要common模块和logging模块只需要引入logging模块即可 -->
+        <dependency>
+            <groupId>com.oying</groupId>
+            <artifactId>oying-logging</artifactId>
+            <version>1.1</version>
+        </dependency>
+
+        <!--邮件依赖-->
+        <dependency>
+            <groupId>javax.mail</groupId>
+            <artifactId>mail</artifactId>
+            <version>${mail.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/oying-tools/src/main/java/com/oying/domain/EmailConfig.java b/oying-tools/src/main/java/com/oying/domain/EmailConfig.java
new file mode 100644
index 0000000..ce9b201
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/domain/EmailConfig.java
@@ -0,0 +1,41 @@
+package com.oying.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+/**
+ * 邮件配置类,数据存覆盖式存入数据存
+ * @author Z
+ * @date 2018-12-26
+ */
+@Data
+@TableName("tool_email_config")
+public class EmailConfig implements Serializable {
+
+    @TableId("config_id")
+    private Long id;
+
+    @NotBlank
+    @ApiModelProperty(value = "邮件服务器SMTP地址")
+    private String host;
+
+    @NotBlank
+    @ApiModelProperty(value = "邮件服务器 SMTP 端口")
+    private String port;
+
+    @NotBlank
+    @ApiModelProperty(value = "发件者用户名")
+    private String user;
+
+    @NotBlank
+    @ApiModelProperty(value = "密码")
+    private String pass;
+
+    @NotBlank
+    @ApiModelProperty(value = "收件人")
+    private String fromUser;
+}
diff --git a/oying-tools/src/main/java/com/oying/domain/LocalStorage.java b/oying-tools/src/main/java/com/oying/domain/LocalStorage.java
new file mode 100644
index 0000000..4f9fd7d
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/domain/LocalStorage.java
@@ -0,0 +1,57 @@
+package com.oying.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.bean.copier.CopyOptions;
+import com.oying.base.BaseEntity;
+import java.io.Serializable;
+
+/**
+* @author Z
+* @date 2019-09-05
+*/
+@Getter
+@Setter
+@NoArgsConstructor
+@TableName("tool_local_storage")
+public class LocalStorage extends BaseEntity implements Serializable {
+
+    @TableId(value = "storage_id", type = IdType.AUTO)
+    @ApiModelProperty(value = "ID", hidden = true)
+    private Long id;
+
+    @ApiModelProperty(value = "真实文件名")
+    private String realName;
+
+    @ApiModelProperty(value = "文件名")
+    private String name;
+
+    @ApiModelProperty(value = "后缀")
+    private String suffix;
+
+    @ApiModelProperty(value = "路径")
+    private String path;
+
+    @ApiModelProperty(value = "类型")
+    private String type;
+
+    @ApiModelProperty(value = "大小")
+    private String size;
+
+    public LocalStorage(String realName,String name, String suffix, String path, String type, String size) {
+        this.realName = realName;
+        this.name = name;
+        this.suffix = suffix;
+        this.path = path;
+        this.type = type;
+        this.size = size;
+    }
+
+    public void copy(LocalStorage source){
+        BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
+    }
+}
diff --git a/oying-tools/src/main/java/com/oying/domain/dto/EmailDto.java b/oying-tools/src/main/java/com/oying/domain/dto/EmailDto.java
new file mode 100644
index 0000000..3119481
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/domain/dto/EmailDto.java
@@ -0,0 +1,32 @@
+package com.oying.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+
+/**
+ * 发送邮件时,接收参数的类
+ * @author Z
+ * @date 2018/09/28 12:02:14
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class EmailDto {
+
+    @NotEmpty
+    @ApiModelProperty(value = "收件人")
+    private List<String> tos;
+
+    @NotBlank
+    @ApiModelProperty(value = "主题")
+    private String subject;
+
+    @NotBlank
+    @ApiModelProperty(value = "内容")
+    private String content;
+}
diff --git a/oying-tools/src/main/java/com/oying/domain/dto/LocalStorageQueryCriteria.java b/oying-tools/src/main/java/com/oying/domain/dto/LocalStorageQueryCriteria.java
new file mode 100644
index 0000000..f75224d
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/domain/dto/LocalStorageQueryCriteria.java
@@ -0,0 +1,26 @@
+package com.oying.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.sql.Timestamp;
+import java.util.List;
+
+/**
+* @author Z
+* @date 2019-09-05
+*/
+@Data
+public class LocalStorageQueryCriteria{
+
+    @ApiModelProperty(value = "模糊查询")
+    private String blurry;
+
+    @ApiModelProperty(value = "创建时间")
+    private List<Timestamp> createTime;
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer page = 1;
+
+    @ApiModelProperty(value = "每页数据量", example = "10")
+    private Integer size = 10;
+}
diff --git a/oying-tools/src/main/java/com/oying/domain/enums/PayStatusEnum.java b/oying-tools/src/main/java/com/oying/domain/enums/PayStatusEnum.java
new file mode 100644
index 0000000..93be469
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/domain/enums/PayStatusEnum.java
@@ -0,0 +1,52 @@
+package com.oying.domain.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 支付状态
+ * @author Z
+ * @date 2018/08/01 16:45:43
+ */
+@Getter
+@AllArgsConstructor
+public enum PayStatusEnum {
+
+    SUCCESS("SUCCESS", "支付成功"),
+
+    REFUND("REFUND", "转入退款"),
+
+    NOTPAY("NOTPAY", "未支付"),
+
+    CLOSED("CLOSED", "已关闭"),
+
+    REVOKED("REVOKED", "已撤销"),
+
+    USERPAYING("USERPAYING", "用户支付中"),
+
+    PAYERROR("PAYERROR", "支付失败"),
+
+    UNKNOWN("UNKNOWN", "未知枚举");
+
+    private final String key;
+
+    private final String value;
+
+    public static PayStatusEnum find(String val) {
+        for (PayStatusEnum value : PayStatusEnum.values()) {
+            if (val.equals(value.getKey())) {
+                return value;
+            }
+        }
+        return UNKNOWN;
+    }
+
+    public static String getValue(String val) {
+        for (PayStatusEnum value : PayStatusEnum.values()) {
+            if (val.equals(value.getKey())) {
+                return value.getValue();
+            }
+        }
+        return UNKNOWN.getValue();
+    }
+}
diff --git a/oying-tools/src/main/java/com/oying/mapper/EmailConfigMapper.java b/oying-tools/src/main/java/com/oying/mapper/EmailConfigMapper.java
new file mode 100644
index 0000000..49bef2d
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/mapper/EmailConfigMapper.java
@@ -0,0 +1,15 @@
+package com.oying.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.oying.domain.EmailConfig;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author Z
+ * @description
+ * @date 2023-06-14
+ **/
+@Mapper
+public interface EmailConfigMapper extends BaseMapper<EmailConfig> {
+
+}
diff --git a/oying-tools/src/main/java/com/oying/mapper/LocalStorageMapper.java b/oying-tools/src/main/java/com/oying/mapper/LocalStorageMapper.java
new file mode 100644
index 0000000..e5b89a0
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/mapper/LocalStorageMapper.java
@@ -0,0 +1,23 @@
+package com.oying.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.domain.LocalStorage;
+import com.oying.domain.dto.LocalStorageQueryCriteria;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+/**
+ * @author Z
+ * @description
+ * @date 2023-06-14
+ **/
+@Mapper
+public interface LocalStorageMapper extends BaseMapper<LocalStorage> {
+
+    IPage<LocalStorage> findAll(@Param("criteria") LocalStorageQueryCriteria criteria, Page<Object> page);
+
+    List<LocalStorage> findAll(@Param("criteria") LocalStorageQueryCriteria criteria);
+}
diff --git a/oying-tools/src/main/java/com/oying/rest/EmailController.java b/oying-tools/src/main/java/com/oying/rest/EmailController.java
new file mode 100644
index 0000000..6d45449
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/rest/EmailController.java
@@ -0,0 +1,48 @@
+package com.oying.rest;
+
+import com.oying.service.EmailService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import com.oying.annotation.Log;
+import com.oying.domain.dto.EmailDto;
+import com.oying.domain.EmailConfig;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 发送邮件
+ * @author Z
+ * @date 2018/09/28 6:55:53
+ */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("api/email")
+@Api(tags = "工具:邮件管理")
+public class EmailController {
+
+    private final EmailService emailService;
+
+    @GetMapping
+    public ResponseEntity<EmailConfig> queryEmailConfig(){
+        return new ResponseEntity<>(emailService.find(),HttpStatus.OK);
+    }
+
+    @Log("配置邮件")
+    @PutMapping
+    @ApiOperation("配置邮件")
+    public ResponseEntity<Object> updateEmailConfig(@Validated @RequestBody EmailConfig emailConfig) throws Exception {
+        emailService.config(emailConfig, emailService.find());
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @Log("发送邮件")
+    @PostMapping
+    @ApiOperation("发送邮件")
+    public ResponseEntity<Object> sendEmail(@Validated @RequestBody EmailDto emailDto){
+        emailService.send(emailDto,emailService.find());
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}
diff --git a/oying-tools/src/main/java/com/oying/rest/LocalStorageController.java b/oying-tools/src/main/java/com/oying/rest/LocalStorageController.java
new file mode 100644
index 0000000..47ffe3b
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/rest/LocalStorageController.java
@@ -0,0 +1,85 @@
+package com.oying.rest;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.oying.service.LocalStorageService;
+import lombok.RequiredArgsConstructor;
+import com.oying.annotation.Log;
+import com.oying.domain.LocalStorage;
+import com.oying.exception.BadRequestException;
+import com.oying.domain.dto.LocalStorageQueryCriteria;
+import com.oying.utils.FileUtil;
+import com.oying.utils.PageResult;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import io.swagger.annotations.*;
+import org.springframework.web.multipart.MultipartFile;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+* @author Z
+* @date 2019-09-05
+*/
+@RestController
+@RequiredArgsConstructor
+@Api(tags = "工具:本地存储管理")
+@RequestMapping("/api/localStorage")
+public class LocalStorageController {
+
+    private final LocalStorageService localStorageService;
+
+    @GetMapping
+    @ApiOperation("查询文件")
+    @PreAuthorize("@el.check('storage:list')")
+    public ResponseEntity<PageResult<LocalStorage>> queryFile(LocalStorageQueryCriteria criteria){
+        Page<Object> page = new Page<>(criteria.getPage(), criteria.getSize());
+        return new ResponseEntity<>(localStorageService.queryAll(criteria,page),HttpStatus.OK);
+    }
+
+    @ApiOperation("导出数据")
+    @GetMapping(value = "/download")
+    @PreAuthorize("@el.check('storage:list')")
+    public void exportFile(HttpServletResponse response, LocalStorageQueryCriteria criteria) throws IOException {
+        localStorageService.download(localStorageService.queryAll(criteria), response);
+    }
+
+    @PostMapping
+    @ApiOperation("上传文件")
+    @PreAuthorize("@el.check('storage:add')")
+    public ResponseEntity<Object> createFile(@RequestParam String name, @RequestParam("file") MultipartFile file){
+        localStorageService.create(name, file);
+        return new ResponseEntity<>(HttpStatus.CREATED);
+    }
+
+    @ApiOperation("上传图片")
+    @PostMapping("/pictures")
+    public ResponseEntity<LocalStorage> uploadPicture(@RequestParam MultipartFile file){
+        // 判断文件是否为图片
+        String suffix = FileUtil.getExtensionName(file.getOriginalFilename());
+        if(!FileUtil.IMAGE.equals(FileUtil.getFileType(suffix))){
+            throw new BadRequestException("只能上传图片");
+        }
+        LocalStorage localStorage = localStorageService.create(null, file);
+        return new ResponseEntity<>(localStorage, HttpStatus.OK);
+    }
+
+    @PutMapping
+    @Log("修改文件")
+    @ApiOperation("修改文件")
+    @PreAuthorize("@el.check('storage:edit')")
+    public ResponseEntity<Object> updateFile(@Validated @RequestBody LocalStorage resources){
+        localStorageService.update(resources);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
+    @Log("删除文件")
+    @DeleteMapping
+    @ApiOperation("多选删除")
+    public ResponseEntity<Object> deleteFile(@RequestBody Long[] ids) {
+        localStorageService.deleteAll(ids);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}
diff --git a/oying-tools/src/main/java/com/oying/service/EmailService.java b/oying-tools/src/main/java/com/oying/service/EmailService.java
new file mode 100644
index 0000000..4fbefa0
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/service/EmailService.java
@@ -0,0 +1,34 @@
+package com.oying.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.domain.dto.EmailDto;
+import com.oying.domain.EmailConfig;
+
+/**
+ * @author Z
+ * @date 2018-12-26
+ */
+public interface EmailService extends IService<EmailConfig> {
+
+    /**
+     * 更新邮件配置
+     * @param emailConfig 邮箱配置
+     * @param old /
+     * @return /
+     * @throws Exception /
+     */
+    EmailConfig config(EmailConfig emailConfig, EmailConfig old) throws Exception;
+
+    /**
+     * 查询配置
+     * @return EmailConfig 邮件配置
+     */
+    EmailConfig find();
+
+    /**
+     * 发送邮件
+     * @param emailDto 邮件发送的内容
+     * @param emailConfig 邮件配置
+     */
+    void send(EmailDto emailDto, EmailConfig emailConfig);
+}
diff --git a/oying-tools/src/main/java/com/oying/service/LocalStorageService.java b/oying-tools/src/main/java/com/oying/service/LocalStorageService.java
new file mode 100644
index 0000000..d86172e
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/service/LocalStorageService.java
@@ -0,0 +1,62 @@
+package com.oying.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.oying.domain.LocalStorage;
+import com.oying.domain.dto.LocalStorageQueryCriteria;
+import com.oying.utils.PageResult;
+import org.springframework.web.multipart.MultipartFile;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+* @author Z
+* @date 2019-09-05
+*/
+public interface LocalStorageService extends IService<LocalStorage> {
+
+    /**
+     * 分页查询
+     *
+     * @param criteria 条件
+     * @param page     分页参数
+     * @return /
+     */
+    PageResult<LocalStorage> queryAll(LocalStorageQueryCriteria criteria, Page<Object> page);
+
+    /**
+     * 查询全部数据
+     * @param criteria 条件
+     * @return /
+     */
+    List<LocalStorage> queryAll(LocalStorageQueryCriteria criteria);
+
+    /**
+     * 上传
+     * @param name 文件名称
+     * @param file 文件
+     * @return /
+     */
+    LocalStorage create(String name, MultipartFile file);
+
+    /**
+     * 编辑
+     * @param resources 文件信息
+     */
+    void update(LocalStorage resources);
+
+    /**
+     * 多选删除
+     * @param ids /
+     */
+    void deleteAll(Long[] ids);
+
+    /**
+     * 导出数据
+     * @param localStorages 待导出的数据
+     * @param response /
+     * @throws IOException /
+     */
+    void download(List<LocalStorage> localStorages, HttpServletResponse response) throws IOException;
+}
diff --git a/oying-tools/src/main/java/com/oying/service/impl/EmailServiceImpl.java b/oying-tools/src/main/java/com/oying/service/impl/EmailServiceImpl.java
new file mode 100644
index 0000000..b75d4ca
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/service/impl/EmailServiceImpl.java
@@ -0,0 +1,91 @@
+package com.oying.service.impl;
+
+import cn.hutool.extra.mail.Mail;
+import cn.hutool.extra.mail.MailAccount;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import com.oying.domain.EmailConfig;
+import com.oying.domain.dto.EmailDto;
+import com.oying.exception.BadRequestException;
+import com.oying.mapper.EmailConfigMapper;
+import com.oying.service.EmailService;
+import com.oying.utils.EncryptUtils;
+import org.springframework.cache.annotation.CacheConfig;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author Z
+ * @date 2018-12-26
+ */
+@Service
+@RequiredArgsConstructor
+@CacheConfig(cacheNames = "email")
+public class EmailServiceImpl extends ServiceImpl<EmailConfigMapper, EmailConfig> implements EmailService {
+
+    @Override
+    @CachePut(key = "'config'")
+    @Transactional(rollbackFor = Exception.class)
+    public EmailConfig config(EmailConfig emailConfig, EmailConfig old) throws Exception {
+        emailConfig.setId(1L);
+        if(!emailConfig.getPass().equals(old.getPass())){
+            // 对称加密
+            emailConfig.setPass(EncryptUtils.desEncrypt(emailConfig.getPass()));
+        }
+        saveOrUpdate(emailConfig);
+        return emailConfig;
+    }
+
+    @Override
+    @Cacheable(key = "'config'")
+    public EmailConfig find() {
+        EmailConfig emailConfig = getById(1L);
+        return emailConfig == null ? new EmailConfig() : emailConfig;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void send(EmailDto emailDto, EmailConfig emailConfig){
+        if(emailConfig.getId() == null){
+            throw new BadRequestException("请先配置,再操作");
+        }
+        // 封装
+        MailAccount account = new MailAccount();
+        // 设置用户
+        String user = emailConfig.getFromUser().split("@")[0];
+        account.setUser(user);
+        account.setHost(emailConfig.getHost());
+        account.setPort(Integer.parseInt(emailConfig.getPort()));
+        account.setAuth(true);
+        try {
+            // 对称解密
+            account.setPass(EncryptUtils.desDecrypt(emailConfig.getPass()));
+        } catch (Exception e) {
+            throw new BadRequestException(e.getMessage());
+        }
+        account.setFrom(emailConfig.getUser()+"<"+emailConfig.getFromUser()+">");
+        // ssl方式发送
+        account.setSslEnable(true);
+        // 使用STARTTLS安全连接
+        account.setStarttlsEnable(true);
+        // 解决jdk8之后默认禁用部分tls协议,导致邮件发送失败的问题
+        account.setSslProtocols("TLSv1 TLSv1.1 TLSv1.2");
+        String content = emailDto.getContent();
+        // 发送
+        try {
+            int size = emailDto.getTos().size();
+            Mail.create(account)
+                    .setTos(emailDto.getTos().toArray(new String[size]))
+                    .setTitle(emailDto.getSubject())
+                    .setContent(content)
+                    .setHtml(true)
+                    //关闭session
+                    .setUseGlobalSession(false)
+                    .send();
+        }catch (Exception e){
+            throw new BadRequestException(e.getMessage());
+        }
+    }
+}
diff --git a/oying-tools/src/main/java/com/oying/service/impl/LocalStorageServiceImpl.java b/oying-tools/src/main/java/com/oying/service/impl/LocalStorageServiceImpl.java
new file mode 100644
index 0000000..f9218ff
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/service/impl/LocalStorageServiceImpl.java
@@ -0,0 +1,110 @@
+package com.oying.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import com.oying.config.properties.FileProperties;
+import com.oying.utils.FileUtil;
+import com.oying.utils.PageResult;
+import com.oying.utils.PageUtil;
+import com.oying.utils.StringUtils;
+import com.oying.domain.LocalStorage;
+import com.oying.domain.dto.LocalStorageQueryCriteria;
+import com.oying.exception.BadRequestException;
+import com.oying.mapper.LocalStorageMapper;
+import com.oying.service.LocalStorageService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.springframework.web.multipart.MultipartFile;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+* @author Z
+* @date 2019-09-05
+*/
+@Service
+@RequiredArgsConstructor
+public class LocalStorageServiceImpl extends ServiceImpl<LocalStorageMapper, LocalStorage> implements LocalStorageService {
+
+    private final LocalStorageMapper localStorageMapper;
+    private final FileProperties properties;
+
+    @Override
+    public PageResult<LocalStorage> queryAll(LocalStorageQueryCriteria criteria, Page<Object> page){
+        return PageUtil.toPage(localStorageMapper.findAll(criteria, page));
+    }
+
+    @Override
+    public List<LocalStorage> queryAll(LocalStorageQueryCriteria criteria){
+        return localStorageMapper.findAll(criteria);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public LocalStorage create(String name, MultipartFile multipartFile) {
+        FileUtil.checkSize(properties.getMaxSize(), multipartFile.getSize());
+        String suffix = FileUtil.getExtensionName(multipartFile.getOriginalFilename());
+        String type = FileUtil.getFileType(suffix);
+        File file = FileUtil.upload(multipartFile, properties.getPath().getPath() + type +  File.separator);
+        if(ObjectUtil.isNull(file)){
+            throw new BadRequestException("上传失败");
+        }
+        try {
+            name = StringUtils.isBlank(name) ? FileUtil.getFileNameNoEx(multipartFile.getOriginalFilename()) : name;
+            LocalStorage localStorage = new LocalStorage(
+                    file.getName(),
+                    name,
+                    suffix,
+                    file.getPath(),
+                    type,
+                    FileUtil.getSize(multipartFile.getSize())
+            );
+            save(localStorage);
+            return localStorage;
+        }catch (Exception e){
+            FileUtil.del(file);
+            throw e;
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void update(LocalStorage resources) {
+        LocalStorage localStorage = getById(resources.getId());
+        localStorage.copy(resources);
+        saveOrUpdate(localStorage);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteAll(Long[] ids) {
+        for (Long id : ids) {
+            LocalStorage storage = getById(id);
+            FileUtil.del(storage.getPath());
+            removeById(storage);
+        }
+    }
+
+    @Override
+    public void download(List<LocalStorage> queryAll, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (LocalStorage localStorage : queryAll) {
+            Map<String,Object> map = new LinkedHashMap<>();
+            map.put("文件名", localStorage.getRealName());
+            map.put("备注名", localStorage.getName());
+            map.put("文件类型", localStorage.getType());
+            map.put("文件大小", localStorage.getSize());
+            map.put("创建者", localStorage.getCreateBy());
+            map.put("创建日期", localStorage.getCreateTime());
+            list.add(map);
+        }
+        FileUtil.downloadExcel(list, response);
+    }
+}
diff --git a/oying-tools/src/main/java/com/oying/utils/PayUtils.java b/oying-tools/src/main/java/com/oying/utils/PayUtils.java
new file mode 100644
index 0000000..9e5f973
--- /dev/null
+++ b/oying-tools/src/main/java/com/oying/utils/PayUtils.java
@@ -0,0 +1,33 @@
+package com.oying.utils;
+
+import org.springframework.stereotype.Component;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * 支付工具类
+ * @author Z
+ * @date 2018/09/30 14:04:35
+ */
+@Component
+public class PayUtils {
+
+    /**
+     * 生成订单号
+     * @return String
+     */
+    public static String getOrderCode() {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        int a = (int)(Math.random() * 9000.0D) + 1000;
+        System.out.println(a);
+        Date date = new Date();
+        String str = sdf.format(date);
+        String[] split = str.split("-");
+        String s = split[0] + split[1] + split[2];
+        String[] split1 = s.split(" ");
+        String s1 = split1[0] + split1[1];
+        String[] split2 = s1.split(":");
+        return split2[0] + split2[1] + split2[2] + a;
+    }
+
+}
diff --git a/oying-tools/src/main/resources/mapper/LocalStorageMapper.xml b/oying-tools/src/main/resources/mapper/LocalStorageMapper.xml
new file mode 100644
index 0000000..f0b02f2
--- /dev/null
+++ b/oying-tools/src/main/resources/mapper/LocalStorageMapper.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.oying.mapper.LocalStorageMapper">
+
+    <resultMap id="BaseResultMap" type="com.oying.domain.LocalStorage">
+        <id column="storage_id" property="id" jdbcType="BIGINT"/>
+        <result column="real_name" property="realName" jdbcType="VARCHAR"/>
+        <result column="name" property="name" jdbcType="VARCHAR"/>
+        <result column="suffix" property="suffix" jdbcType="VARCHAR"/>
+        <result column="size" property="size" jdbcType="VARCHAR"/>
+        <result column="type" property="type" jdbcType="VARCHAR"/>
+        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
+        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
+        <result column="create_by" property="createBy" jdbcType="TIMESTAMP"/>
+        <result column="update_by" property="updateBy" jdbcType="TIMESTAMP"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        storage_id, real_name, name, suffix, size, type, create_time, update_time, create_by, update_by
+    </sql>
+
+    <select id="findAll" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List"/>
+        from tool_local_storage
+        <where>
+            <if test="criteria.blurry != null and criteria.blurry != ''">
+                AND (
+                    name LIKE CONCAT('%',#{criteria.blurry},'%')
+                    OR suffix LIKE CONCAT('%',#{criteria.blurry},'%')
+                    OR type LIKE CONCAT('%',#{criteria.blurry},'%')
+                    OR create_by LIKE CONCAT('%',#{criteria.blurry},'%')
+                )
+            </if>
+            <if test="criteria.createTime != null and criteria.createTime.size() > 0">
+                AND update_time BETWEEN #{criteria.createTime[0]} AND #{criteria.createTime[1]}
+            </if>
+        </where>
+        ORDER BY storage_id DESC
+    </select>
+</mapper>
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..a2fef79
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,264 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.oying</groupId>
+    <artifactId>oying</artifactId>
+    <packaging>pom</packaging>
+    <version>1.1</version>
+
+    <modules>
+        <module>oying-common</module>
+        <module>oying-logging</module>
+        <module>oying-system</module>
+        <module>oying-tools</module>
+        <module>oying-generator</module>
+    </modules>
+
+    <name>OYING 后台管理</name>
+    <url>https://oying.vip</url>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.18</version>
+    </parent>
+
+    <properties>
+        <logback.version>1.2.9</logback.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <java.version>1.8</java.version>
+        <fastjson2.version>2.0.54</fastjson2.version>
+        <druid.version>1.2.19</druid.version>
+        <commons-pool2.version>2.11.1</commons-pool2.version>
+    </properties>
+
+    <dependencies>
+        <!--Spring boot Web容器-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <!-- 去掉Jackson依赖,用fastjson -->
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-json</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!--Spring boot 测试-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!--Spring boot 安全框架-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <!-- spring boot 验证 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <!-- spring boot 缓存 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+
+        <!--Spring boot Redis-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <!--Spring boot redisson-->
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-boot-starter</artifactId>
+            <version>3.17.1</version>
+        </dependency>
+
+        <!-- Spring boot websocket -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+
+        <!-- mybatis -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.5.3.1</version>
+        </dependency>
+
+        <!--spring boot 集成redis所需common-pool2-->
+        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+            <version>${commons-pool2.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <!--监控sql日志-->
+        <dependency>
+            <groupId>p6spy</groupId>
+            <artifactId>p6spy</artifactId>
+            <version>3.9.1</version>
+        </dependency>
+
+        <!-- Swagger UI 相关 -->
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+            <version>3.0.3</version>
+            <exclusions>
+                <!-- 去掉 swagger-annotations 依赖,避免冲突 -->
+                <exclusion>
+                    <groupId>io.swagger</groupId>
+                    <artifactId>swagger-annotations</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- 添加swagger-annotations依赖 -->
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+            <version>1.5.22</version>
+        </dependency>
+
+        <!--Mysql依赖包-->
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+            <version>9.2.0</version>
+            <scope>runtime</scope>
+        </dependency>
+
+
+        <!-- druid数据源驱动 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>${druid.version}</version>
+        </dependency>
+
+        <!-- IP地址解析库 -->
+        <dependency>
+            <groupId>net.dreamlu</groupId>
+            <artifactId>mica-ip2region</artifactId>
+            <version>2.7.18.9</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjrt</artifactId>
+            <version>1.9.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjweaver</artifactId>
+            <version>1.9.7</version>
+        </dependency>
+
+        <!--lombok插件-->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- excel工具 -->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>5.4.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>5.4.0</version>
+        </dependency>
+        <dependency>
+            <groupId>xerces</groupId>
+            <artifactId>xercesImpl</artifactId>
+            <version>2.12.2</version>
+        </dependency>
+
+        <!-- fastjson2 -->
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+            <version>${fastjson2.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2-extension-spring5</artifactId>
+            <version>${fastjson2.version}</version>
+        </dependency>
+
+        <!-- Java图形验证码 -->
+        <dependency>
+            <groupId>com.github.whvcse</groupId>
+            <artifactId>easy-captcha</artifactId>
+            <version>1.6.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-text</artifactId>
+            <version>1.13.0</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <!-- 打包时跳过测试 -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <repositories>
+        <repository>
+            <id>public</id>
+            <name>aliyun nexus</name>
+            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+        </repository>
+    </repositories>
+
+    <pluginRepositories>
+        <pluginRepository>
+            <id>public</id>
+            <name>aliyun nexus</name>
+            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </pluginRepository>
+    </pluginRepositories>
+</project>

--
Gitblit v1.9.3