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} >= ${symbol}{criteria.${column.changeColumnName}}
+ </#if>
+ <#if column.queryType = '<='>
+ and ${column.columnName} <= ${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 ©${.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 ©${.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