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