From 72807fa65cb3e5ac6afaefa12111bd6969f522be Mon Sep 17 00:00:00 2001 From: Qyu Date: Tue, 28 Oct 2025 21:07:30 +0800 Subject: [PATCH] 111 --- huacai-framework/pom.xml | 64 ++ .../framework/aspectj/DataScopeAspect.java | 213 ++++++ .../framework/aspectj/DataSourceAspect.java | 88 +++ .../huacai/framework/aspectj/LogAspect.java | 319 ++++++++ .../framework/aspectj/RateLimiterAspect.java | 125 +++ .../framework/config/ApplicationConfig.java | 40 + .../framework/config/CaptchaConfig.java | 83 ++ .../huacai/framework/config/DruidConfig.java | 166 ++++ .../config/FastJson2JsonRedisSerializer.java | 80 ++ .../huacai/framework/config/FilterConfig.java | 89 +++ .../framework/config/KaptchaTextCreator.java | 91 +++ .../framework/config/MyBatisConfig.java | 186 +++++ .../huacai/framework/config/RedisConfig.java | 95 +++ .../framework/config/ResourcesConfig.java | 89 +++ .../framework/config/SecurityConfig.java | 148 ++++ .../huacai/framework/config/ServerConfig.java | 46 ++ .../framework/config/ThreadPoolConfig.java | 84 +++ .../huacai/framework/config/WebMvcConfig.java | 22 + .../config/properties/DruidProperties.java | 89 +++ .../properties/PermitAllUrlProperties.java | 73 ++ .../datasource/DynamicDataSource.java | 44 ++ .../DynamicDataSourceContextHolder.java | 45 ++ .../DemoEnvironmentInterceptor.java | 60 ++ .../interceptor/RepeatSubmitInterceptor.java | 77 ++ .../impl/SameUrlDataInterceptor.java | 145 ++++ .../framework/manager/AsyncManager.java | 70 ++ .../framework/manager/ShutdownManager.java | 50 ++ .../manager/factory/AsyncFactory.java | 102 +++ .../context/AuthenticationContextHolder.java | 46 ++ .../context/PermissionContextHolder.java | 43 ++ .../filter/JwtAuthenticationTokenFilter.java | 66 ++ .../handle/AuthenticationEntryPointImpl.java | 48 ++ .../handle/LogoutSuccessHandlerImpl.java | 62 ++ .../huacai/framework/web/domain/Server.java | 302 ++++++++ .../framework/web/domain/server/Cpu.java | 129 ++++ .../framework/web/domain/server/Jvm.java | 162 ++++ .../framework/web/domain/server/Mem.java | 81 ++ .../framework/web/domain/server/Sys.java | 95 +++ .../framework/web/domain/server/SysFile.java | 129 ++++ .../web/exception/GlobalExceptionHandler.java | 192 +++++ .../web/service/PermissionService.java | 182 +++++ .../web/service/SysLoginService.java | 181 +++++ .../web/service/SysPasswordService.java | 118 +++ .../web/service/SysPermissionService.java | 97 +++ .../web/service/SysRegisterService.java | 144 ++++ .../framework/web/service/TokenService.java | 280 +++++++ .../web/service/UserDetailsServiceImpl.java | 94 +++ .../framework/aspectj/DataScopeAspect.class | Bin 0 -> 5462 bytes .../framework/aspectj/DataSourceAspect.class | Bin 0 -> 2773 bytes .../huacai/framework/aspectj/LogAspect.class | Bin 0 -> 9860 bytes .../framework/aspectj/RateLimiterAspect.class | Bin 0 -> 4930 bytes .../framework/config/ApplicationConfig.class | Bin 0 -> 1874 bytes .../framework/config/CaptchaConfig.class | Bin 0 -> 2438 bytes .../framework/config/DruidConfig$1.class | Bin 0 -> 2112 bytes .../huacai/framework/config/DruidConfig.class | Bin 0 -> 4776 bytes .../config/FastJson2JsonRedisSerializer.class | Bin 0 -> 2763 bytes .../framework/config/FilterConfig.class | Bin 0 -> 2391 bytes .../framework/config/KaptchaTextCreator.class | Bin 0 -> 1701 bytes .../framework/config/MyBatisConfig.class | Bin 0 -> 6355 bytes .../huacai/framework/config/RedisConfig.class | Bin 0 -> 3104 bytes .../framework/config/ResourcesConfig.class | Bin 0 -> 4071 bytes .../framework/config/SecurityConfig.class | Bin 0 -> 9255 bytes .../framework/config/ServerConfig.class | Bin 0 -> 1426 bytes .../framework/config/ThreadPoolConfig$1.class | Bin 0 -> 1304 bytes .../framework/config/ThreadPoolConfig.class | Bin 0 -> 2318 bytes .../framework/config/WebMvcConfig.class | Bin 0 -> 1519 bytes .../config/properties/DruidProperties.class | Bin 0 -> 2682 bytes .../properties/PermitAllUrlProperties.class | Bin 0 -> 5919 bytes .../datasource/DynamicDataSource.class | Bin 0 -> 1154 bytes .../DynamicDataSourceContextHolder.class | Bin 0 -> 1292 bytes .../DemoEnvironmentInterceptor.class | Bin 0 -> 1932 bytes .../interceptor/RepeatSubmitInterceptor.class | Bin 0 -> 2087 bytes .../impl/SameUrlDataInterceptor.class | Bin 0 -> 4561 bytes .../framework/manager/AsyncManager.class | Bin 0 -> 1443 bytes .../framework/manager/ShutdownManager.class | Bin 0 -> 1337 bytes .../manager/factory/AsyncFactory$1.class | Bin 0 -> 3197 bytes .../manager/factory/AsyncFactory$2.class | Bin 0 -> 1201 bytes .../manager/factory/AsyncFactory.class | Bin 0 -> 2038 bytes .../context/AuthenticationContextHolder.class | Bin 0 -> 1164 bytes .../context/PermissionContextHolder.class | Bin 0 -> 1156 bytes .../filter/JwtAuthenticationTokenFilter.class | Bin 0 -> 2883 bytes .../handle/AuthenticationEntryPointImpl.class | Bin 0 -> 1875 bytes .../handle/LogoutSuccessHandlerImpl.class | Bin 0 -> 2780 bytes .../huacai/framework/web/domain/Server.class | Bin 0 -> 8221 bytes .../framework/web/domain/server/Cpu.class | Bin 0 -> 1608 bytes .../framework/web/domain/server/Jvm.class | Bin 0 -> 2489 bytes .../framework/web/domain/server/Mem.class | Bin 0 -> 1094 bytes .../framework/web/domain/server/Sys.class | Bin 0 -> 1353 bytes .../framework/web/domain/server/SysFile.class | Bin 0 -> 1731 bytes .../exception/GlobalExceptionHandler.class | Bin 0 -> 7232 bytes .../web/service/PermissionService.class | Bin 0 -> 3529 bytes .../web/service/SysLoginService.class | Bin 0 -> 6511 bytes .../web/service/SysPasswordService.class | Bin 0 -> 3411 bytes .../web/service/SysPermissionService.class | Bin 0 -> 2635 bytes .../web/service/SysRegisterService.class | Bin 0 -> 4152 bytes .../framework/web/service/TokenService.class | Bin 0 -> 7376 bytes .../web/service/UserDetailsServiceImpl.class | Bin 0 -> 3388 bytes huacai-generator/pom.xml | 40 + .../huacai/generator/config/GenConfig.java | 116 +++ .../generator/controller/GenController.java | 298 ++++++++ .../com/huacai/generator/domain/GenTable.java | 437 +++++++++++ .../generator/domain/GenTableColumn.java | 492 ++++++++++++ .../mapper/GenTableColumnMapper.java | 60 ++ .../generator/mapper/GenTableMapper.java | 83 ++ .../service/GenTableColumnServiceImpl.java | 75 ++ .../service/GenTableServiceImpl.java | 576 ++++++++++++++ .../service/IGenTableColumnService.java | 44 ++ .../generator/service/IGenTableService.java | 121 +++ .../com/huacai/generator/util/GenUtils.java | 302 ++++++++ .../generator/util/VelocityInitializer.java | 34 + .../huacai/generator/util/VelocityUtils.java | 477 ++++++++++++ .../src/main/resources/generator.yml | 10 + .../mapper/generator/GenTableColumnMapper.xml | 182 +++++ .../mapper/generator/GenTableMapper.xml | 304 ++++++++ .../main/resources/vm/java/controller.java.vm | 162 ++++ .../src/main/resources/vm/java/domain.java.vm | 59 ++ .../src/main/resources/vm/java/mapper.java.vm | 91 +++ .../main/resources/vm/java/service.java.vm | 69 ++ .../resources/vm/java/serviceImpl.java.vm | 211 ++++++ .../main/resources/vm/java/sub-domain.java.vm | 47 ++ .../src/main/resources/vm/js/api.js.vm | 44 ++ .../src/main/resources/vm/sql/sql.vm | 22 + .../main/resources/vm/vue/index-tree.vue.vm | 503 +++++++++++++ .../src/main/resources/vm/vue/index.vue.vm | 712 ++++++++++++++++++ .../resources/vm/vue/v3/index-tree.vue.vm | 474 ++++++++++++ .../src/main/resources/vm/vue/v3/index.vue.vm | 590 +++++++++++++++ .../src/main/resources/vm/xml/mapper.xml.vm | 135 ++++ .../huacai/generator/config/GenConfig.class | Bin 0 -> 1589 bytes .../generator/controller/GenController.class | Bin 0 -> 7878 bytes .../huacai/generator/domain/GenTable.class | Bin 0 -> 7906 bytes .../generator/domain/GenTableColumn.class | Bin 0 -> 7466 bytes .../mapper/GenTableColumnMapper.class | Bin 0 -> 803 bytes .../generator/mapper/GenTableMapper.class | Bin 0 -> 988 bytes .../service/GenTableColumnServiceImpl.class | Bin 0 -> 1642 bytes .../service/GenTableServiceImpl.class | Bin 0 -> 15977 bytes .../service/IGenTableColumnService.class | Bin 0 -> 510 bytes .../generator/service/IGenTableService.class | Bin 0 -> 1304 bytes .../com/huacai/generator/util/GenUtils.class | Bin 0 -> 5827 bytes .../generator/util/VelocityInitializer.class | Bin 0 -> 1070 bytes .../huacai/generator/util/VelocityUtils.class | Bin 0 -> 11005 bytes huacai-generator/target/classes/generator.yml | 10 + .../mapper/generator/GenTableColumnMapper.xml | 127 ++++ .../mapper/generator/GenTableMapper.xml | 206 +++++ .../target/classes/vm/java/controller.java.vm | 143 ++++ .../target/classes/vm/java/domain.java.vm | 61 ++ .../target/classes/vm/java/mapper.java.vm | 91 +++ .../target/classes/vm/java/service.java.vm | 69 ++ .../classes/vm/java/serviceImpl.java.vm | 211 ++++++ .../target/classes/vm/java/sub-domain.java.vm | 47 ++ .../target/classes/vm/js/api.js.vm | 44 ++ huacai-generator/target/classes/vm/sql/sql.vm | 22 + .../target/classes/vm/vue/index-tree.vue.vm | 503 +++++++++++++ .../target/classes/vm/vue/index.vue.vm | 712 ++++++++++++++++++ .../classes/vm/vue/v3/index-tree.vue.vm | 474 ++++++++++++ .../target/classes/vm/vue/v3/index.vue.vm | 590 +++++++++++++++ .../target/classes/vm/xml/mapper.xml.vm | 135 ++++ huacai-quartz/pom.xml | 40 + .../huacai/quartz/config/ScheduleConfig.java | 57 ++ .../quartz/controller/SysJobController.java | 235 ++++++ .../controller/SysJobLogController.java | 111 +++ .../java/com/huacai/quartz/domain/SysJob.java | 192 +++++ .../com/huacai/quartz/domain/SysJobLog.java | 234 ++++++ .../huacai/quartz/mapper/SysJobLogMapper.java | 64 ++ .../huacai/quartz/mapper/SysJobMapper.java | 67 ++ .../quartz/service/ISysJobLogService.java | 56 ++ .../huacai/quartz/service/ISysJobService.java | 102 +++ .../service/impl/SysJobLogServiceImpl.java | 87 +++ .../service/impl/SysJobServiceImpl.java | 296 ++++++++ .../java/com/huacai/quartz/task/RyTask.java | 37 + .../huacai/quartz/util/AbstractQuartzJob.java | 141 ++++ .../com/huacai/quartz/util/CronUtils.java | 73 ++ .../com/huacai/quartz/util/JobInvokeUtil.java | 238 ++++++ .../QuartzDisallowConcurrentExecution.java | 33 + .../quartz/util/QuartzJobExecution.java | 28 + .../com/huacai/quartz/util/ScheduleUtils.java | 192 +++++ .../mapper/quartz/SysJobLogMapper.xml | 151 ++++ .../resources/mapper/quartz/SysJobMapper.xml | 201 +++++ .../quartz/controller/SysJobController.class | Bin 0 -> 6976 bytes .../controller/SysJobLogController.class | Bin 0 -> 3756 bytes .../com/huacai/quartz/domain/SysJob.class | Bin 0 -> 4889 bytes .../com/huacai/quartz/domain/SysJobLog.class | Bin 0 -> 3392 bytes .../quartz/mapper/SysJobLogMapper.class | Bin 0 -> 726 bytes .../huacai/quartz/mapper/SysJobMapper.class | Bin 0 -> 676 bytes .../quartz/service/ISysJobLogService.class | Bin 0 -> 614 bytes .../quartz/service/ISysJobService.class | Bin 0 -> 906 bytes .../service/impl/SysJobLogServiceImpl.class | Bin 0 -> 1787 bytes .../service/impl/SysJobServiceImpl.class | Bin 0 -> 5801 bytes .../com/huacai/quartz/task/RyTask.class | Bin 0 -> 1530 bytes .../quartz/util/AbstractQuartzJob.class | Bin 0 -> 3882 bytes .../com/huacai/quartz/util/CronUtils.class | Bin 0 -> 1283 bytes .../huacai/quartz/util/JobInvokeUtil.class | Bin 0 -> 5661 bytes .../QuartzDisallowConcurrentExecution.class | Bin 0 -> 889 bytes .../quartz/util/QuartzJobExecution.class | Bin 0 -> 761 bytes .../huacai/quartz/util/ScheduleUtils.class | Bin 0 -> 6371 bytes .../classes/mapper/quartz/SysJobLogMapper.xml | 93 +++ .../classes/mapper/quartz/SysJobMapper.xml | 111 +++ 196 files changed, 18288 insertions(+) create mode 100644 huacai-framework/pom.xml create mode 100644 huacai-framework/src/main/java/com/huacai/framework/aspectj/DataScopeAspect.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/aspectj/DataSourceAspect.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/aspectj/LogAspect.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/aspectj/RateLimiterAspect.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/ApplicationConfig.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/CaptchaConfig.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/DruidConfig.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/FastJson2JsonRedisSerializer.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/FilterConfig.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/KaptchaTextCreator.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/MyBatisConfig.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/RedisConfig.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/ResourcesConfig.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/SecurityConfig.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/ServerConfig.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/ThreadPoolConfig.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/WebMvcConfig.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/properties/DruidProperties.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/config/properties/PermitAllUrlProperties.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/datasource/DynamicDataSource.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/datasource/DynamicDataSourceContextHolder.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/interceptor/DemoEnvironmentInterceptor.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/interceptor/RepeatSubmitInterceptor.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/interceptor/impl/SameUrlDataInterceptor.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/manager/AsyncManager.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/manager/ShutdownManager.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/manager/factory/AsyncFactory.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/security/context/AuthenticationContextHolder.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/security/context/PermissionContextHolder.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/security/filter/JwtAuthenticationTokenFilter.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/security/handle/AuthenticationEntryPointImpl.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/security/handle/LogoutSuccessHandlerImpl.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/domain/Server.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Cpu.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Jvm.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Mem.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Sys.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/domain/server/SysFile.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/exception/GlobalExceptionHandler.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/service/PermissionService.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/service/SysLoginService.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/service/SysPasswordService.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/service/SysPermissionService.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/service/SysRegisterService.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/service/TokenService.java create mode 100644 huacai-framework/src/main/java/com/huacai/framework/web/service/UserDetailsServiceImpl.java create mode 100644 huacai-framework/target/classes/com/huacai/framework/aspectj/DataScopeAspect.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/aspectj/DataSourceAspect.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/aspectj/LogAspect.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/aspectj/RateLimiterAspect.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/ApplicationConfig.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/CaptchaConfig.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/DruidConfig$1.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/DruidConfig.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/FastJson2JsonRedisSerializer.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/FilterConfig.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/KaptchaTextCreator.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/MyBatisConfig.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/RedisConfig.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/ResourcesConfig.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/SecurityConfig.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/ServerConfig.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/ThreadPoolConfig$1.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/ThreadPoolConfig.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/WebMvcConfig.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/properties/DruidProperties.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/config/properties/PermitAllUrlProperties.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/datasource/DynamicDataSource.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/datasource/DynamicDataSourceContextHolder.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/interceptor/DemoEnvironmentInterceptor.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/interceptor/RepeatSubmitInterceptor.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/interceptor/impl/SameUrlDataInterceptor.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/manager/AsyncManager.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/manager/ShutdownManager.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/manager/factory/AsyncFactory$1.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/manager/factory/AsyncFactory$2.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/manager/factory/AsyncFactory.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/security/context/AuthenticationContextHolder.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/security/context/PermissionContextHolder.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/security/filter/JwtAuthenticationTokenFilter.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/security/handle/AuthenticationEntryPointImpl.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/security/handle/LogoutSuccessHandlerImpl.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/domain/Server.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/domain/server/Cpu.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/domain/server/Jvm.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/domain/server/Mem.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/domain/server/Sys.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/domain/server/SysFile.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/exception/GlobalExceptionHandler.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/service/PermissionService.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/service/SysLoginService.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/service/SysPasswordService.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/service/SysPermissionService.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/service/SysRegisterService.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/service/TokenService.class create mode 100644 huacai-framework/target/classes/com/huacai/framework/web/service/UserDetailsServiceImpl.class create mode 100644 huacai-generator/pom.xml create mode 100644 huacai-generator/src/main/java/com/huacai/generator/config/GenConfig.java create mode 100644 huacai-generator/src/main/java/com/huacai/generator/controller/GenController.java create mode 100644 huacai-generator/src/main/java/com/huacai/generator/domain/GenTable.java create mode 100644 huacai-generator/src/main/java/com/huacai/generator/domain/GenTableColumn.java create mode 100644 huacai-generator/src/main/java/com/huacai/generator/mapper/GenTableColumnMapper.java create mode 100644 huacai-generator/src/main/java/com/huacai/generator/mapper/GenTableMapper.java create mode 100644 huacai-generator/src/main/java/com/huacai/generator/service/GenTableColumnServiceImpl.java create mode 100644 huacai-generator/src/main/java/com/huacai/generator/service/GenTableServiceImpl.java create mode 100644 huacai-generator/src/main/java/com/huacai/generator/service/IGenTableColumnService.java create mode 100644 huacai-generator/src/main/java/com/huacai/generator/service/IGenTableService.java create mode 100644 huacai-generator/src/main/java/com/huacai/generator/util/GenUtils.java create mode 100644 huacai-generator/src/main/java/com/huacai/generator/util/VelocityInitializer.java create mode 100644 huacai-generator/src/main/java/com/huacai/generator/util/VelocityUtils.java create mode 100644 huacai-generator/src/main/resources/generator.yml create mode 100644 huacai-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml create mode 100644 huacai-generator/src/main/resources/mapper/generator/GenTableMapper.xml create mode 100644 huacai-generator/src/main/resources/vm/java/controller.java.vm create mode 100644 huacai-generator/src/main/resources/vm/java/domain.java.vm create mode 100644 huacai-generator/src/main/resources/vm/java/mapper.java.vm create mode 100644 huacai-generator/src/main/resources/vm/java/service.java.vm create mode 100644 huacai-generator/src/main/resources/vm/java/serviceImpl.java.vm create mode 100644 huacai-generator/src/main/resources/vm/java/sub-domain.java.vm create mode 100644 huacai-generator/src/main/resources/vm/js/api.js.vm create mode 100644 huacai-generator/src/main/resources/vm/sql/sql.vm create mode 100644 huacai-generator/src/main/resources/vm/vue/index-tree.vue.vm create mode 100644 huacai-generator/src/main/resources/vm/vue/index.vue.vm create mode 100644 huacai-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm create mode 100644 huacai-generator/src/main/resources/vm/vue/v3/index.vue.vm create mode 100644 huacai-generator/src/main/resources/vm/xml/mapper.xml.vm create mode 100644 huacai-generator/target/classes/com/huacai/generator/config/GenConfig.class create mode 100644 huacai-generator/target/classes/com/huacai/generator/controller/GenController.class create mode 100644 huacai-generator/target/classes/com/huacai/generator/domain/GenTable.class create mode 100644 huacai-generator/target/classes/com/huacai/generator/domain/GenTableColumn.class create mode 100644 huacai-generator/target/classes/com/huacai/generator/mapper/GenTableColumnMapper.class create mode 100644 huacai-generator/target/classes/com/huacai/generator/mapper/GenTableMapper.class create mode 100644 huacai-generator/target/classes/com/huacai/generator/service/GenTableColumnServiceImpl.class create mode 100644 huacai-generator/target/classes/com/huacai/generator/service/GenTableServiceImpl.class create mode 100644 huacai-generator/target/classes/com/huacai/generator/service/IGenTableColumnService.class create mode 100644 huacai-generator/target/classes/com/huacai/generator/service/IGenTableService.class create mode 100644 huacai-generator/target/classes/com/huacai/generator/util/GenUtils.class create mode 100644 huacai-generator/target/classes/com/huacai/generator/util/VelocityInitializer.class create mode 100644 huacai-generator/target/classes/com/huacai/generator/util/VelocityUtils.class create mode 100644 huacai-generator/target/classes/generator.yml create mode 100644 huacai-generator/target/classes/mapper/generator/GenTableColumnMapper.xml create mode 100644 huacai-generator/target/classes/mapper/generator/GenTableMapper.xml create mode 100644 huacai-generator/target/classes/vm/java/controller.java.vm create mode 100644 huacai-generator/target/classes/vm/java/domain.java.vm create mode 100644 huacai-generator/target/classes/vm/java/mapper.java.vm create mode 100644 huacai-generator/target/classes/vm/java/service.java.vm create mode 100644 huacai-generator/target/classes/vm/java/serviceImpl.java.vm create mode 100644 huacai-generator/target/classes/vm/java/sub-domain.java.vm create mode 100644 huacai-generator/target/classes/vm/js/api.js.vm create mode 100644 huacai-generator/target/classes/vm/sql/sql.vm create mode 100644 huacai-generator/target/classes/vm/vue/index-tree.vue.vm create mode 100644 huacai-generator/target/classes/vm/vue/index.vue.vm create mode 100644 huacai-generator/target/classes/vm/vue/v3/index-tree.vue.vm create mode 100644 huacai-generator/target/classes/vm/vue/v3/index.vue.vm create mode 100644 huacai-generator/target/classes/vm/xml/mapper.xml.vm create mode 100644 huacai-quartz/pom.xml create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/config/ScheduleConfig.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/controller/SysJobController.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/controller/SysJobLogController.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/domain/SysJob.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/domain/SysJobLog.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/mapper/SysJobLogMapper.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/mapper/SysJobMapper.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/service/ISysJobLogService.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/service/ISysJobService.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/service/impl/SysJobLogServiceImpl.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/service/impl/SysJobServiceImpl.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/task/RyTask.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/util/AbstractQuartzJob.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/util/CronUtils.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/util/JobInvokeUtil.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/util/QuartzDisallowConcurrentExecution.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/util/QuartzJobExecution.java create mode 100644 huacai-quartz/src/main/java/com/huacai/quartz/util/ScheduleUtils.java create mode 100644 huacai-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml create mode 100644 huacai-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/controller/SysJobController.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/controller/SysJobLogController.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/domain/SysJob.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/domain/SysJobLog.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/mapper/SysJobLogMapper.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/mapper/SysJobMapper.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/service/ISysJobLogService.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/service/ISysJobService.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/service/impl/SysJobLogServiceImpl.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/service/impl/SysJobServiceImpl.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/task/RyTask.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/util/AbstractQuartzJob.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/util/CronUtils.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/util/JobInvokeUtil.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/util/QuartzDisallowConcurrentExecution.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/util/QuartzJobExecution.class create mode 100644 huacai-quartz/target/classes/com/huacai/quartz/util/ScheduleUtils.class create mode 100644 huacai-quartz/target/classes/mapper/quartz/SysJobLogMapper.xml create mode 100644 huacai-quartz/target/classes/mapper/quartz/SysJobMapper.xml diff --git a/huacai-framework/pom.xml b/huacai-framework/pom.xml new file mode 100644 index 0000000..266d78e --- /dev/null +++ b/huacai-framework/pom.xml @@ -0,0 +1,64 @@ + + + + huacai + com.huacai + 3.8.7 + + 4.0.0 + + huacai-framework + + + framework框架核心 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + com.alibaba + druid-spring-boot-starter + + + + + pro.fessional + kaptcha + + + servlet-api + javax.servlet + + + + + + + com.github.oshi + oshi-core + + + + + com.huacai + huacai-system + + + + + diff --git a/huacai-framework/src/main/java/com/huacai/framework/aspectj/DataScopeAspect.java b/huacai-framework/src/main/java/com/huacai/framework/aspectj/DataScopeAspect.java new file mode 100644 index 0000000..083b2fa --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/aspectj/DataScopeAspect.java @@ -0,0 +1,213 @@ +package com.huacai.framework.aspectj; + +import java.util.ArrayList; +import java.util.List; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; +import com.huacai.common.annotation.DataScope; +import com.huacai.common.core.domain.BaseEntity; +import com.huacai.common.core.domain.entity.SysRole; +import com.huacai.common.core.domain.entity.SysUser; +import com.huacai.common.core.domain.model.LoginUser; +import com.huacai.common.core.text.Convert; +import com.huacai.common.utils.SecurityUtils; +import com.huacai.common.utils.StringUtils; +import com.huacai.framework.security.context.PermissionContextHolder; + +/** + * 数据过滤处理切面 + * 用于通过AOP实现基于角色的数据权限控制,动态拼接数据过滤SQL + * + * @author huacai + */ +// 标记为AOP切面类 +@Aspect +// 注册为Spring组件 +@Component +public class DataScopeAspect +{ + /** + * 全部数据权限(可查看所有数据) + */ + public static final String DATA_SCOPE_ALL = "1"; + + /** + * 自定义数据权限(可查看指定部门数据) + */ + public static final String DATA_SCOPE_CUSTOM = "2"; + + /** + * 部门数据权限(可查看本部门数据) + */ + public static final String DATA_SCOPE_DEPT = "3"; + + /** + * 部门及以下数据权限(可查看本部门及子部门数据) + */ + public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; + + /** + * 仅本人数据权限(仅可查看自己的数据) + */ + public static final String DATA_SCOPE_SELF = "5"; + + /** + * 数据权限过滤关键字(用于在SQL中占位) + */ + public static final String DATA_SCOPE = "dataScope"; + + /** + * 方法执行前拦截,处理数据权限 + * + * @param point 切点对象 + * @param controllerDataScope 方法上的@DataScope注解对象 + * @throws Throwable 可能的异常 + */ + @Before("@annotation(controllerDataScope)") // 切点表达式:拦截所有带有@DataScope注解的方法 + public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable + { + // 清除之前可能残留的数据权限参数,防止干扰 + clearDataScope(point); + // 处理数据权限,生成过滤SQL + handleDataScope(point, controllerDataScope); + } + + /** + * 处理数据权限核心方法 + * + * @param joinPoint 切点对象 + * @param controllerDataScope @DataScope注解对象 + */ + protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) + { + // 获取当前登录用户信息 + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNotNull(loginUser)) + { + SysUser currentUser = loginUser.getUser(); + // 如果是超级管理员,则不过滤数据(直接放行) + if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) + { + // 获取注解中的权限字符(默认为上下文权限) + String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext()); + // 生成数据权限过滤SQL + dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), + controllerDataScope.userAlias(), permission); + } + } + } + + /** + * 数据范围过滤,生成过滤SQL并设置到参数中 + * + * @param joinPoint 切点对象 + * @param user 当前用户对象 + * @param deptAlias SQL中部门表的别名 + * @param userAlias SQL中用户表的别名 + * @param permission 权限字符 + */ + public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission) + { + // 构建数据过滤SQL片段 + StringBuilder sqlString = new StringBuilder(); + // 记录已处理的权限类型,避免重复 + List conditions = new ArrayList(); + + // 遍历用户拥有的角色,根据角色的数据权限生成过滤条件 + for (SysRole role : user.getRoles()) + { + String dataScope = role.getDataScope(); + // 如果不是自定义权限且已处理过该权限类型,则跳过 + if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) + { + continue; + } + // 如果角色没有指定的权限,则跳过 + if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions()) + && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) + { + continue; + } + + // 根据数据权限类型拼接SQL条件 + if (DATA_SCOPE_ALL.equals(dataScope)) + { + // 全部数据权限:清空SQL(无需过滤) + sqlString = new StringBuilder(); + conditions.add(dataScope); + break; // 已满足最高权限,无需继续处理其他角色 + } + else if (DATA_SCOPE_CUSTOM.equals(dataScope)) + { + // 自定义数据权限:查询角色关联的部门 + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, + role.getRoleId())); + } + else if (DATA_SCOPE_DEPT.equals(dataScope)) + { + // 本部门数据权限:部门ID等于用户所属部门 + sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); + } + else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) + { + // 本部门及子部门数据权限:部门ID在用户部门及子部门中 + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", + deptAlias, user.getDeptId(), user.getDeptId())); + } + else if (DATA_SCOPE_SELF.equals(dataScope)) + { + // 仅本人数据权限:用户ID等于当前用户 + if (StringUtils.isNotBlank(userAlias)) + { + sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); + } + else + { + // 没有用户别名时,设置一个无效条件(不查询任何数据) + sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); + } + } + // 记录已处理的权限类型 + conditions.add(dataScope); + } + + // 如果没有匹配的权限条件,设置无效条件(不查询任何数据) + if (StringUtils.isEmpty(conditions)) + { + sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); + } + + // 如果生成了有效的SQL条件,则设置到方法参数中 + if (StringUtils.isNotBlank(sqlString.toString())) + { + // 获取方法的第一个参数(假设为BaseEntity子类) + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) + { + BaseEntity baseEntity = (BaseEntity) params; + // 将SQL条件设置到参数的扩展属性中(用于MyBatis在XML中拼接) + baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); + // 注:substring(4)是为了去掉开头的" OR " + } + } + } + + /** + * 拼接权限SQL前先清空params.dataScope参数,防止SQL注入或残留条件干扰 + */ + private void clearDataScope(final JoinPoint joinPoint) + { + // 获取方法的第一个参数(假设为BaseEntity子类) + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) + { + BaseEntity baseEntity = (BaseEntity) params; + // 清空数据权限参数 + baseEntity.getParams().put(DATA_SCOPE, ""); + } + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/aspectj/DataSourceAspect.java b/huacai-framework/src/main/java/com/huacai/framework/aspectj/DataSourceAspect.java new file mode 100644 index 0000000..05e660c --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/aspectj/DataSourceAspect.java @@ -0,0 +1,88 @@ +package com.huacai.framework.aspectj; + +import java.util.Objects; +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.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import com.huacai.common.annotation.DataSource; +import com.huacai.common.utils.StringUtils; +import com.huacai.framework.datasource.DynamicDataSourceContextHolder; + +/** + * 多数据源处理 + * 用于实现基于注解的动态数据源切换功能 + * + * @author huacai + */ +// 标记此类为AOP切面类,用于定义切入点和通知 +@Aspect +// 设置切面执行顺序为1(值越小优先级越高),确保在事务等其他切面之前执行 +@Order(1) +// 标记为Spring组件,使其被容器扫描并管理 +@Component +public class DataSourceAspect +{ + // 日志记录器,用于记录数据源切换相关日志 + protected Logger logger = LoggerFactory.getLogger(getClass()); + + // 定义切入点:匹配所有标注了@DataSource注解的方法,或包含@DataSource注解的类中的所有方法 + @Pointcut("@annotation(com.huacai.common.annotation.DataSource)" + + "|| @within(com.huacai.common.annotation.DataSource)") + public void dsPointCut() + { + // 切入点方法体为空,仅作为标记 + } + + // 定义环绕通知,在切入点匹配的方法执行前后进行增强处理 + @Around("dsPointCut()") + public Object around(ProceedingJoinPoint point) throws Throwable + { + // 获取当前方法或类上的DataSource注解 + DataSource dataSource = getDataSource(point); + + // 如果注解存在(即需要切换数据源) + if (StringUtils.isNotNull(dataSource)) + { + // 将数据源类型存入上下文持有器,供动态数据源选择使用 + DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); + } + + try + { + // 执行目标方法(即被切入的业务方法) + return point.proceed(); + } + finally + { + // 方法执行完成后,清除数据源上下文,避免影响后续操作 + DynamicDataSourceContextHolder.clearDataSourceType(); + } + } + + /** + * 获取需要切换的数据源 + * 优先获取方法上的@DataSource注解,若方法上没有则获取类上的注解 + */ + public DataSource getDataSource(ProceedingJoinPoint point) + { + // 获取连接点的方法签名,用于获取目标方法信息 + MethodSignature signature = (MethodSignature) point.getSignature(); + // 从目标方法上查找DataSource注解 + DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); + // 如果方法上存在注解,直接返回 + if (Objects.nonNull(dataSource)) + { + return dataSource; + } + + // 方法上没有注解时,从目标类上查找DataSource注解并返回 + return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/aspectj/LogAspect.java b/huacai-framework/src/main/java/com/huacai/framework/aspectj/LogAspect.java new file mode 100644 index 0000000..f0f1f81 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/aspectj/LogAspect.java @@ -0,0 +1,319 @@ +package com.huacai.framework.aspectj; + +import java.util.Collection; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ArrayUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.NamedThreadLocal; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; +import com.alibaba.fastjson2.JSON; +import com.huacai.common.annotation.Log; +import com.huacai.common.core.domain.entity.SysUser; +import com.huacai.common.core.domain.model.LoginUser; +import com.huacai.common.enums.BusinessStatus; +import com.huacai.common.enums.HttpMethod; +import com.huacai.common.filter.PropertyPreExcludeFilter; +import com.huacai.common.utils.SecurityUtils; +import com.huacai.common.utils.ServletUtils; +import com.huacai.common.utils.StringUtils; +import com.huacai.common.utils.ip.IpUtils; +import com.huacai.framework.manager.AsyncManager; +import com.huacai.framework.manager.factory.AsyncFactory; +import com.huacai.system.domain.SysOperLog; + +/** + * 操作日志记录处理切面 + * 用于通过AOP拦截带有@Log注解的方法,记录操作日志信息 + * + * @author huacai + */ +// 标记此类为AOP切面类 +@Aspect +// 注册为Spring组件,使其被容器管理 +@Component +public class LogAspect +{ + // 定义日志记录器,用于记录切面内部的日志信息 + private static final Logger log = LoggerFactory.getLogger(LogAspect.class); + + /** 排除敏感属性字段(如密码等,避免日志中记录敏感信息) */ + public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; + + /** 计算操作消耗时间的线程本地变量(每个线程独立存储,避免线程安全问题) */ + private static final ThreadLocal TIME_THREADLOCAL = new NamedThreadLocal("Cost Time"); + + /** + * 处理请求前执行的方法 + * 用于记录方法执行的开始时间 + * + * @param joinPoint 切点对象,包含被拦截方法的信息 + * @param controllerLog 方法上的@Log注解对象,包含日志配置信息 + */ + @Before(value = "@annotation(controllerLog)") // 切点表达式:拦截所有带有@Log注解的方法 + public void boBefore(JoinPoint joinPoint, Log controllerLog) + { + // 记录当前时间戳到线程本地变量,用于后续计算方法执行耗时 + TIME_THREADLOCAL.set(System.currentTimeMillis()); + } + + /** + * 处理完请求后执行的方法(正常返回时) + * 用于在方法成功执行后记录日志 + * + * @param joinPoint 切点对象 + * @param controllerLog @Log注解对象 + * @param jsonResult 方法返回的结果对象 + */ + @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) + { + // 调用日志处理核心方法,异常参数为null表示无异常 + handleLog(joinPoint, controllerLog, null, jsonResult); + } + + /** + * 拦截异常操作(方法抛出异常时执行) + * 用于在方法执行异常时记录日志 + * + * @param joinPoint 切点对象 + * @param controllerLog @Log注解对象 + * @param e 抛出的异常对象 + */ + @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) + { + // 调用日志处理核心方法,传入异常对象 + handleLog(joinPoint, controllerLog, e, null); + } + + /** + * 日志处理核心方法 + * 封装操作日志信息并异步保存到数据库 + * + * @param joinPoint 切点对象 + * @param controllerLog @Log注解对象 + * @param e 异常对象(可能为null) + * @param jsonResult 方法返回结果(可能为null) + */ + protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) + { + try + { + // 获取当前登录用户信息(从Security上下文获取) + LoginUser loginUser = SecurityUtils.getLoginUser(); + + // *========数据库日志实体构建=========*// + SysOperLog operLog = new SysOperLog(); + // 默认设置操作状态为成功 + operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); + // 获取客户端IP地址 + String ip = IpUtils.getIpAddr(); + operLog.setOperIp(ip); + // 获取请求URL并截断(避免过长) + operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); + + // 如果存在登录用户,设置操作人及部门信息 + if (loginUser != null) + { + operLog.setOperName(loginUser.getUsername()); + SysUser currentUser = loginUser.getUser(); + if (StringUtils.isNotNull(currentUser) && StringUtils.isNotNull(currentUser.getDept())) + { + operLog.setDeptName(currentUser.getDept().getDeptName()); + } + } + + // 如果存在异常,更新操作状态为失败并记录异常信息 + if (e != null) + { + operLog.setStatus(BusinessStatus.FAIL.ordinal()); + operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); // 截断异常信息避免过长 + } + + // 设置操作方法名(全类名.方法名()) + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + operLog.setMethod(className + "." + methodName + "()"); + + // 设置请求方式(GET/POST等) + operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); + + // 处理@Log注解上的参数,完善日志信息 + getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); + + // 计算方法执行耗时(当前时间 - 开始时间) + operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get()); + + // 异步执行日志保存(避免阻塞主线程) + AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); + } + catch (Exception exp) + { + // 记录本地异常日志(切面自身异常) + log.error("异常信息:{}", exp.getMessage()); + exp.printStackTrace(); + } + finally + { + // 清除线程本地变量,避免内存泄漏 + TIME_THREADLOCAL.remove(); + } + } + + /** + * 获取注解中对方法的描述信息,用于完善操作日志 + * + * @param joinPoint 切点对象 + * @param log @Log注解对象 + * @param operLog 操作日志实体 + * @param jsonResult 方法返回结果 + * @throws Exception 可能的异常 + */ + public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception + { + // 设置业务类型(从注解中获取) + operLog.setBusinessType(log.businessType().ordinal()); + // 设置操作标题(从注解中获取) + operLog.setTitle(log.title()); + // 设置操作人类别(从注解中获取) + operLog.setOperatorType(log.operatorType().ordinal()); + + // 如果注解配置需要保存请求数据,则处理请求参数 + if (log.isSaveRequestData()) + { + // 获取请求参数并设置到日志实体 + setRequestValue(joinPoint, operLog, log.excludeParamNames()); + } + + // 如果注解配置需要保存响应数据且结果不为空,则处理响应结果 + if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) + { + // 将响应结果转为JSON并截断(避免过长) + operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); + } + } + + /** + * 处理请求参数,将其保存到日志实体中 + * + * @param joinPoint 切点对象 + * @param operLog 操作日志实体 + * @param excludeParamNames 注解中指定的需要排除的参数名 + * @throws Exception 可能的异常 + */ + private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception + { + // 获取请求参数Map(从request中获取) + Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); + String requestMethod = operLog.getRequestMethod(); + + // 如果参数Map为空,且是PUT/POST请求(通常这类请求参数在请求体中) + if (StringUtils.isEmpty(paramsMap) + && (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))) + { + // 从方法参数中解析请求参数 + String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); // 截断参数避免过长 + } + else + { + // 将参数Map转为JSON,同时排除敏感字段 + operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000)); + } + } + + /** + * 将方法参数数组拼接为字符串 + * + * @param paramsArray 方法参数数组 + * @param excludeParamNames 需要排除的参数名 + * @return 拼接后的参数字符串 + */ + private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) + { + String params = ""; + if (paramsArray != null && paramsArray.length > 0) + { + for (Object o : paramsArray) + { + // 过滤空对象和不需要记录的对象(如文件上传、请求响应对象等) + if (StringUtils.isNotNull(o) && !isFilterObject(o)) + { + try + { + // 将参数对象转为JSON,同时排除敏感字段 + String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); + params += jsonObj.toString() + " "; + } + catch (Exception e) + { + // 转换失败时忽略(避免影响主流程) + } + } + } + } + return params.trim(); // 去除首尾空格 + } + + /** + * 创建属性过滤规则,用于排除敏感字段(如密码) + * + * @param excludeParamNames 额外需要排除的参数名 + * @return 属性过滤对象 + */ + public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) + { + // 合并默认排除字段和注解中指定的排除字段 + return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); + } + + /** + * 判断是否为需要过滤的对象(不记录到日志中) + * + * @param o 待判断的对象 + * @return true-需要过滤,false-不需要过滤 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) + { + Class clazz = o.getClass(); + + // 如果是数组,判断元素是否为文件上传对象 + if (clazz.isArray()) + { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } + // 如果是集合,判断元素是否为文件上传对象 + else if (Collection.class.isAssignableFrom(clazz)) + { + Collection collection = (Collection) o; + for (Object value : collection) + { + return value instanceof MultipartFile; + } + } + // 如果是Map,判断值是否为文件上传对象 + else if (Map.class.isAssignableFrom(clazz)) + { + Map map = (Map) o; + for (Object value : map.entrySet()) + { + Map.Entry entry = (Map.Entry) value; + return entry.getValue() instanceof MultipartFile; + } + } + // 直接判断是否为文件上传、请求、响应或参数绑定结果对象 + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/aspectj/RateLimiterAspect.java b/huacai-framework/src/main/java/com/huacai/framework/aspectj/RateLimiterAspect.java new file mode 100644 index 0000000..857d7e7 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/aspectj/RateLimiterAspect.java @@ -0,0 +1,125 @@ +package com.huacai.framework.aspectj; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; +import com.huacai.common.annotation.RateLimiter; +import com.huacai.common.enums.LimitType; +import com.huacai.common.exception.ServiceException; +import com.huacai.common.utils.StringUtils; +import com.huacai.common.utils.ip.IpUtils; + +/** + * 限流处理切面 + * 用于通过AOP拦截带有@RateLimiter注解的方法,实现基于Redis的分布式限流 + * + * @author huacai + */ +// 标记为AOP切面类 +@Aspect +// 注册为Spring组件 +@Component +public class RateLimiterAspect +{ + // 日志记录器 + private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); + + // Redis模板(用于执行Lua脚本) + private RedisTemplate redisTemplate; + + // 限流Lua脚本(从RedisConfig中注入) + private RedisScript limitScript; + + // 注入RedisTemplate(setter注入,避免循环依赖) + @Autowired + public void setRedisTemplate1(RedisTemplate redisTemplate) + { + this.redisTemplate = redisTemplate; + } + + // 注入限流脚本 + @Autowired + public void setLimitScript(RedisScript limitScript) + { + this.limitScript = limitScript; + } + + /** + * 方法执行前拦截,执行限流逻辑 + * + * @param point 切点对象 + * @param rateLimiter @RateLimiter注解对象 + * @throws Throwable 限流异常或其他异常 + */ + @Before("@annotation(rateLimiter)") // 切点表达式:拦截所有带有@RateLimiter注解的方法 + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable + { + // 从注解中获取限流时间窗口(秒) + int time = rateLimiter.time(); + // 从注解中获取时间窗口内的最大请求数 + int count = rateLimiter.count(); + + // 生成限流key(结合注解key、限流类型和方法信息) + String combineKey = getCombineKey(rateLimiter, point); + // 将key放入集合(Lua脚本需要KEYS参数) + List keys = Collections.singletonList(combineKey); + + try + { + // 执行Redis限流脚本,返回当前请求计数 + Long number = redisTemplate.execute(limitScript, keys, count, time); + // 如果计数超过限制,抛出限流异常 + if (StringUtils.isNull(number) || number.intValue() > count) + { + throw new ServiceException("访问过于频繁,请稍候再试"); + } + // 记录限流日志 + log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey); + } + catch (ServiceException e) + { + // 限流异常直接抛出 + throw e; + } + catch (Exception e) + { + // 其他异常包装为运行时异常 + throw new RuntimeException("服务器限流异常,请稍候再试"); + } + } + + /** + * 生成限流的唯一key + * 格式:注解key + 限流类型(如IP) + 类名 + 方法名 + * + * @param rateLimiter @RateLimiter注解对象 + * @param point 切点对象 + * @return 限流key + */ + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) + { + StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); + // 如果限流类型为IP,则拼接客户端IP + if (rateLimiter.limitType() == LimitType.IP) + { + stringBuffer.append(IpUtils.getIpAddr()).append("-"); + } + // 获取方法签名信息 + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + // 拼接类名和方法名 + stringBuffer.append(targetClass.getName()).append("-").append(method.getName()); + return stringBuffer.toString(); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/ApplicationConfig.java b/huacai-framework/src/main/java/com/huacai/framework/config/ApplicationConfig.java new file mode 100644 index 0000000..7c16f3a --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/ApplicationConfig.java @@ -0,0 +1,40 @@ +package com.huacai.framework.config; + +import java.util.TimeZone; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * 程序注解配置 + * + * @author huacai + */ +/** + * 程序注解配置类 + * 用于配置Spring相关的注解扫描、AOP代理及其他全局配置 + * + * @author huacai + */ +// 标记此类为Spring配置类,相当于XML配置文件 +@Configuration +// 启用AspectJ自动代理,exposeProxy = true表示暴露代理对象,允许通过AopContext访问当前代理对象 +@EnableAspectJAutoProxy(exposeProxy = true) +// 指定MyBatis Mapper接口的扫描路径,自动生成Mapper实现类 +@MapperScan("com.huacai.**.mapper") +public class ApplicationConfig +{ + /** + * 配置Jackson的时区信息 + * 用于统一处理JSON序列化/反序列化时的时区问题 + */ + // 定义一个Bean,用于自定义Jackson的ObjectMapper构建器 + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() + { + // 返回一个自定义配置器,设置时区为系统默认时区 + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/CaptchaConfig.java b/huacai-framework/src/main/java/com/huacai/framework/config/CaptchaConfig.java new file mode 100644 index 0000000..4231a0d --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/CaptchaConfig.java @@ -0,0 +1,83 @@ +package com.huacai.framework.config; + +import java.util.Properties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import static com.google.code.kaptcha.Constants.*; + +/** + * 验证码配置 + * + * @author huacai + */ +@Configuration +public class CaptchaConfig +{ + @Bean(name = "captchaProducer") + public DefaultKaptcha getKaptchaBean() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + + @Bean(name = "captchaProducerMath") + public DefaultKaptcha getKaptchaBeanMath() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.huacai.framework.config.KaptchaTextCreator"); + // 验证码文本字符间距 默认为2 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/DruidConfig.java b/huacai-framework/src/main/java/com/huacai/framework/config/DruidConfig.java new file mode 100644 index 0000000..e64160b --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/DruidConfig.java @@ -0,0 +1,166 @@ +package com.huacai.framework.config; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.sql.DataSource; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; +import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; +import com.huacai.common.enums.DataSourceType; +import com.huacai.common.utils.spring.SpringUtils; +import com.huacai.framework.config.properties.DruidProperties; +import com.huacai.framework.datasource.DynamicDataSource; + +/** + * Druid数据源配置类 + * 用于配置多数据源(主从库)及去除监控页面广告 + * + * @author huacai + */ +// 标记为Spring配置类 +@Configuration +public class DruidConfig +{ + /** + * 配置主数据源 + * + * @param druidProperties Druid数据源配置属性 + * @return 主数据源对象 + */ + @Bean + @ConfigurationProperties("spring.datasource.druid.master") // 绑定配置文件中的主库配置 + public DataSource masterDataSource(DruidProperties druidProperties) + { + // 构建Druid数据源 + DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); + // 应用配置属性(如用户名、密码、连接池参数等) + return druidProperties.dataSource(dataSource); + } + + /** + * 配置从数据源(条件注解:仅当配置文件中启用从库时才创建) + * + * @param druidProperties Druid数据源配置属性 + * @return 从数据源对象 + */ + @Bean + @ConfigurationProperties("spring.datasource.druid.slave") // 绑定配置文件中的从库配置 + @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true") + public DataSource slaveDataSource(DruidProperties druidProperties) + { + DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); + return druidProperties.dataSource(dataSource); + } + + /** + * 配置动态数据源(主从切换) + * 以主数据源为默认数据源 + * + * @param masterDataSource 主数据源 + * @return 动态数据源对象 + */ + @Bean(name = "dynamicDataSource") + @Primary // 优先使用该数据源 + public DynamicDataSource dataSource(DataSource masterDataSource) + { + // 存储备选数据源(主库+从库) + Map targetDataSources = new HashMap<>(); + targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); + // 添加从数据源(如果存在) + setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource"); + // 创建动态数据源(默认数据源为主库) + return new DynamicDataSource(masterDataSource, targetDataSources); + } + + /** + * 向数据源映射中添加数据源(如从库) + * + * @param targetDataSources 数据源映射Map + * @param sourceName 数据源名称(如SLAVE) + * @param beanName 数据源在Spring中的Bean名称 + */ + public void setDataSource(Map targetDataSources, String sourceName, String beanName) + { + try + { + // 从Spring容器中获取数据源Bean(如果存在) + DataSource dataSource = SpringUtils.getBean(beanName); + targetDataSources.put(sourceName, dataSource); + } + catch (Exception e) + { + // 从库不存在时忽略(不影响主库使用) + } + } + + /** + * 去除Druid监控页面底部的广告 + * 通过过滤器修改common.js内容,移除广告相关代码 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true") + public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) + { + // 获取Druid监控页面的配置参数 + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + // 提取监控页面的URL模式(默认为/druid/*) + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; + // 构建common.js的URL模式(如/druid/js/common.js) + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + // common.js在Druid jar包中的路径 + final String filePath = "support/http/resources/js/common.js"; + + // 创建自定义过滤器,用于修改common.js内容 + Filter filter = new Filter() + { + @Override + public void init(javax.servlet.FilterConfig filterConfig) throws ServletException + { + // 初始化方法(无操作) + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + // 先执行过滤器链(获取原始的common.js内容) + chain.doFilter(request, response); + // 重置响应缓冲区(不重置响应头) + response.resetBuffer(); + // 从Druid资源中读取common.js内容 + String text = Utils.readFromResource(filePath); + // 正则替换,去除底部的广告链接 + text = text.replaceAll("
", ""); + text = text.replaceAll("powered.*?shrek.wang", ""); + // 将修改后的内容写入响应 + response.getWriter().write(text); + } + + @Override + public void destroy() + { + // 销毁方法(无操作) + } + }; + + // 注册过滤器,只对common.js的URL生效 + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/FastJson2JsonRedisSerializer.java b/huacai-framework/src/main/java/com/huacai/framework/config/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..0d1709b --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/FastJson2JsonRedisSerializer.java @@ -0,0 +1,80 @@ +package com.huacai.framework.config; + +import java.nio.charset.Charset; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.filter.Filter; +import com.huacai.common.constant.Constants; + +/** + * Redis使用FastJson序列化 + * 自定义Redis序列化器,基于FastJson2实现对象与字节数组的相互转换 + * + * @author huacai + * @param 序列化的对象类型 + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer +{ + // 默认字符集为UTF-8,用于字节数组与字符串的转换 + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + // 自动类型过滤器,基于系统常量中的JSON白名单字符串创建,用于反序列化时限制可解析的类 + static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR); + + // 序列化的目标类类型 + private Class clazz; + + /** + * 构造方法,指定序列化的目标类 + * + * @param clazz 要序列化的对象类型 + */ + public FastJson2JsonRedisSerializer(Class clazz) + { + super(); + this.clazz = clazz; + } + + /** + * 将对象序列化为字节数组 + * + * @param t 要序列化的对象 + * @return 序列化后的字节数组,若对象为null则返回空字节数组 + * @throws SerializationException 序列化过程中发生异常时抛出 + */ + @Override + public byte[] serialize(T t) throws SerializationException + { + if (t == null) + { + return new byte[0]; + } + // 使用FastJson将对象转为JSON字符串,开启WriteClassName特性以记录类名(用于反序列化) + // 再将JSON字符串按默认字符集转为字节数组 + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + /** + * 将字节数组反序列化为对象 + * + * @param bytes 要反序列化的字节数组 + * @return 反序列化后的对象,若字节数组为null或空则返回null + * @throws SerializationException 反序列化过程中发生异常时抛出 + */ + @Override + public T deserialize(byte[] bytes) throws SerializationException + { + if (bytes == null || bytes.length <= 0) + { + return null; + } + // 将字节数组按默认字符集转为JSON字符串 + String str = new String(bytes, DEFAULT_CHARSET); + + // 使用FastJson将JSON字符串解析为指定类型的对象,应用自动类型过滤器限制可解析的类 + return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/FilterConfig.java b/huacai-framework/src/main/java/com/huacai/framework/config/FilterConfig.java new file mode 100644 index 0000000..a25b123 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/FilterConfig.java @@ -0,0 +1,89 @@ +package com.huacai.framework.config; + +import java.util.HashMap; +import java.util.Map; +import javax.servlet.DispatcherType; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.huacai.common.filter.RepeatableFilter; +import com.huacai.common.filter.XssFilter; +import com.huacai.common.utils.StringUtils; + +/** + * Filter配置类 + * 用于注册和配置应用中的过滤器,如XSS过滤器和可重复读取请求体的过滤器 + * + * @author huacai + */ +@Configuration +public class FilterConfig +{ + // 从配置文件中读取XSS过滤的排除路径(不需要过滤的URL) + @Value("${xss.excludes}") + private String excludes; + + // 从配置文件中读取XSS过滤的URL模式(需要过滤的URL) + @Value("${xss.urlPatterns}") + private String urlPatterns; + + /** + * 注册XSS过滤器 + * 仅当配置文件中xss.enabled=true时才会创建该Bean + * + * @return 配置好的XSS过滤器注册Bean + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") + public FilterRegistrationBean xssFilterRegistration() + { + // 创建过滤器注册Bean + FilterRegistrationBean registration = new FilterRegistrationBean(); + // 设置过滤器适用的调度类型(此处为请求类型) + registration.setDispatcherTypes(DispatcherType.REQUEST); + // 设置要注册的过滤器实例(XSS过滤器,用于防止XSS攻击) + registration.setFilter(new XssFilter()); + // 拆分URL模式字符串为数组,设置过滤器拦截的URL + registration.addUrlPatterns(StringUtils.split(urlPatterns, ",")); + // 设置过滤器名称 + registration.setName("xssFilter"); + // 设置过滤器执行顺序(最高优先级,优先于其他过滤器执行) + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + // 创建初始化参数Map + Map initParameters = new HashMap(); + // 设置排除路径参数(从配置文件读取的excludes) + initParameters.put("excludes", excludes); + // 为过滤器设置初始化参数 + registration.setInitParameters(initParameters); + // 返回配置好的过滤器注册Bean + return registration; + } + + /** + * 注册可重复读取请求体的过滤器 + * 用于解决请求体只能读取一次的问题,方便后续过滤器或处理器多次读取 + * + * @return 配置好的RepeatableFilter注册Bean + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public FilterRegistrationBean someFilterRegistration() + { + // 创建过滤器注册Bean + FilterRegistrationBean registration = new FilterRegistrationBean(); + // 设置要注册的过滤器实例(RepeatableFilter) + registration.setFilter(new RepeatableFilter()); + // 设置过滤器拦截所有URL + registration.addUrlPatterns("/*"); + // 设置过滤器名称 + registration.setName("repeatableFilter"); + // 设置过滤器执行顺序(最低优先级,在其他过滤器之后执行) + registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + // 返回配置好的过滤器注册Bean + return registration; + } + +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/KaptchaTextCreator.java b/huacai-framework/src/main/java/com/huacai/framework/config/KaptchaTextCreator.java new file mode 100644 index 0000000..49cab60 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/KaptchaTextCreator.java @@ -0,0 +1,91 @@ +package com.huacai.framework.config; + +import java.util.Random; +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +/** + * 验证码文本生成器 + * 继承 * 继承默认文本创建器,自定义验证码文本生成逻辑,生成包含简单数学运算的验证码 + * + * @author huacai + */ +public class KaptchaTextCreator extends DefaultTextCreator +{ + // 数字字符串字符串数组,包含0-10的字符串表示 + private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); + + /** + * 生成验证码文本 + * 生成包含简单数学运算(加、减、乘)的表达式,并在表达式后拼接结果(用于后续验证) + * + * @return 包含数学运算的验证码文本,格式为"运算式=?@结果" + */ + @Override + public String getText() + { + // 存储运算结果 + Integer result = 0; + // 随机数生成器 + Random random = new Random(); + // 生成两个0-9之间的随机数 + int x = random.nextInt(10); + int y = random.nextInt(10); + // 用于构建验证码文本的字符串构建器 + StringBuilder suChinese = new StringBuilder(); + // 随机生成运算类型(0-乘法,1-除法/加法,2-减法) + int randomoperands = random.nextInt(3); + + // 处理乘法运算 + if (randomoperands == 0) + { + result = x * y; + suChinese.append(CNUMBERS[x]); // 拼接第一个数字 + suChinese.append("*"); // 拼接乘号 + suChinese.append(CNUMBERS[y]); // 拼接第二个数字 + } + // 处理除法或加法运算 + else if (randomoperands == 1) + { + // 若x不为0且y能被x整除,则进行除法运算 + if ((x != 0) && y % x == 0) + { + result = y / x; + suChinese.append(CNUMBERS[y]); // 拼接被除数 + suChinese.append("/"); // 拼接除号 + suChinese.append(CNUMBERS[x]); // 拼接除数 + } + // 否则进行加法运算 + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); // 拼接第一个数字 + suChinese.append("+"); // 拼接加号 + suChinese.append(CNUMBERS[y]); // 拼接第二个数字 + } + } + // 处理减法运算 + else + { + // 若x大于等于y,用x减y + if (x >= y) + { + result = x - y; + suChinese.append(CNUMBERS[x]); // 拼接被减数 + suChinese.append("-"); // 拼接减号 + suChinese.append(CNUMBERS[y]); // 拼接减数 + } + // 否则用y减x(保证结果非负) + else + { + result = y - x; + suChinese.append(CNUMBERS[y]); // 拼接被减数 + suChinese.append("-"); // 拼接减号 + suChinese.append(CNUMBERS[x]); // 拼接减数 + } + } + // 拼接表达式后缀和结果(格式为"=?@结果",用于后续解析验证) + suChinese.append("=?@" + result); + // 返回生成的验证码文本 + return suChinese.toString(); + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/MyBatisConfig.java b/huacai-framework/src/main/java/com/huacai/framework/config/MyBatisConfig.java new file mode 100644 index 0000000..7a11439 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/MyBatisConfig.java @@ -0,0 +1,186 @@ +package com.huacai.framework.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import javax.sql.DataSource; +import org.apache.ibatis.io.VFS; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.util.ClassUtils; +import com.huacai.common.utils.StringUtils; + +/** + * Mybatis支持*匹配扫描包的配置类 + * 用于扩展MyBatis的包扫描能力,支持通配符匹配多个包路径 + * + * @author huacai + */ +@Configuration +public class MyBatisConfig +{ + // 注入环境变量对象,用于读取配置文件中的MyBatis相关配置 + @Autowired + private Environment env; + + // 默认的资源匹配模式,用于扫描类文件 + static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; + + /** + * 处理类型别名包路径,支持通配符扫描 + * 将包含通配符的包路径转换为实际存在的包路径集合 + * + * @param typeAliasesPackage 配置的类型别名包路径(可能包含通配符) + * @return 处理后的实际包路径,多个包用逗号分隔 + */ + public static String setTypeAliasesPackage(String typeAliasesPackage) + { + // 创建资源模式解析器,用于解析资源路径 + ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver(); + // 创建元数据读取工厂,用于读取类资源的元数据 + MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver); + // 存储所有匹配到的包路径 + List allResult = new ArrayList(); + try + { + // 按逗号分割配置的包路径,处理多个包的情况 + for (String aliasesPackage : typeAliasesPackage.split(",")) + { + // 存储当前包路径匹配到的结果 + List result = new ArrayList(); + // 构建资源扫描路径:classpath*: + 转换为资源路径的包名 + /**/*.class + aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN; + // 根据路径获取所有匹配的资源(.class文件) + Resource[] resources = resolver.getResources(aliasesPackage); + // 遍历资源,提取包路径 + if (resources != null && resources.length > 0) + { + MetadataReader metadataReader = null; + for (Resource resource : resources) + { + // 仅处理可读的资源 + if (resource.isReadable()) + { + // 读取资源的元数据 + metadataReader = metadataReaderFactory.getMetadataReader(resource); + try + { + // 获取类的全限定名,提取其所在的包路径并添加到结果中 + result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName()); + } + catch (ClassNotFoundException e) + { + e.printStackTrace(); + } + } + } + } + // 去重并添加到总结果中 + if (result.size() > 0) + { + HashSet hashResult = new HashSet(result); + allResult.addAll(hashResult); + } + } + // 将去重后的包路径拼接为字符串返回 + if (allResult.size() > 0) + { + typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0])); + } + else + { + // 未找到任何包时抛出异常 + throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包"); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + return typeAliasesPackage; + } + + /** + * 解析Mapper文件路径,将配置的路径转换为Resource数组 + * + * @param mapperLocations 配置的Mapper文件路径数组 + * @return 解析后的Resource数组 + */ + public Resource[] resolveMapperLocations(String[] mapperLocations) + { + // 创建资源模式解析器 + ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); + // 存储解析后的资源 + List resources = new ArrayList(); + if (mapperLocations != null) + { + // 遍历每个Mapper路径,解析为Resource对象 + for (String mapperLocation : mapperLocations) + { + try + { + // 根据路径获取所有匹配的资源(Mapper.xml文件) + Resource[] mappers = resourceResolver.getResources(mapperLocation); + resources.addAll(Arrays.asList(mappers)); + } + catch (IOException e) + { + // 忽略解析异常 + } + } + } + // 转换为Resource数组返回 + return resources.toArray(new Resource[resources.size()]); + } + + /** + * 创建SqlSessionFactory实例,配置MyBatis的核心参数 + * + * @param dataSource 数据源对象 + * @return 配置好的SqlSessionFactory实例 + * @throws Exception 配置过程中发生异常时抛出 + */ + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception + { + // 从环境变量中读取MyBatis配置的类型别名包路径 + String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage"); + // 从环境变量中读取Mapper文件路径 + String mapperLocations = env.getProperty("mybatis.mapperLocations"); + // 从环境变量中读取MyBatis配置文件路径 + String configLocation = env.getProperty("mybatis.configLocation"); + + // 处理类型别名包路径,支持通配符 + typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage); + // 添加SpringBoot的VFS实现,用于扫描类路径下的资源 + VFS.addImplClass(SpringBootVFS.class); + + // 创建SqlSessionFactoryBean + final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); + // 设置数据源 + sessionFactory.setDataSource(dataSource); + // 设置处理后的类型别名包路径 + sessionFactory.setTypeAliasesPackage(typeAliasesPackage); + // 解析并设置Mapper文件路径 + sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ","))); + // 设置MyBatis配置文件路径 + sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); + // 创建并返回SqlSessionFactory实例 + return sessionFactory.getObject(); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/RedisConfig.java b/huacai-framework/src/main/java/com/huacai/framework/config/RedisConfig.java new file mode 100644 index 0000000..8870620 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/RedisConfig.java @@ -0,0 +1,95 @@ +package com.huacai.framework.config; + +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * Redis配置类 + * 用于配置RedisTemplate和限流脚本 + * + * @author huacai + */ +// 标记为Spring配置类 +@Configuration +// 启用缓存注解支持(如@Cacheable等) +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport +{ + /** + * 配置RedisTemplate + * 用于自定义Redis的序列化方式 + * + * @param connectionFactory Redis连接工厂 + * @return 配置后的RedisTemplate + */ + @Bean + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) + { + RedisTemplate template = new RedisTemplate<>(); + // 设置Redis连接工厂 + template.setConnectionFactory(connectionFactory); + + // 使用FastJson2作为JSON序列化器 + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + + // 使用StringRedisSerializer序列化Redis的key + template.setKeySerializer(new StringRedisSerializer()); + // 使用FastJson2序列化Redis的value + template.setValueSerializer(serializer); + + // Hash的key也使用StringRedisSerializer序列化 + template.setHashKeySerializer(new StringRedisSerializer()); + // Hash的value使用FastJson2序列化 + template.setHashValueSerializer(serializer); + + // 初始化RedisTemplate + template.afterPropertiesSet(); + return template; + } + + /** + * 配置限流脚本(基于Redis的Lua脚本) + * 用于实现分布式限流功能 + * + * @return Redis脚本对象 + */ + @Bean + public DefaultRedisScript limitScript() + { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + // 设置脚本内容 + redisScript.setScriptText(limitScriptText()); + // 设置脚本返回值类型 + redisScript.setResultType(Long.class); + return redisScript; + } + + /** + * 限流Lua脚本内容 + * 逻辑:在指定时间窗口内限制请求次数 + * + * @return Lua脚本字符串 + */ + private String limitScriptText() + { + return "local key = KEYS[1]\n" + // 限流key(如接口+IP) + "local count = tonumber(ARGV[1])\n" + // 最大请求次数 + "local time = tonumber(ARGV[2])\n" + // 时间窗口(秒) + "local current = redis.call('get', key);\n" + // 获取当前计数 + "if current and tonumber(current) > count then\n" + // 如果当前计数超过限制 + " return tonumber(current);\n" + // 返回当前计数(表示限流) + "end\n" + + "current = redis.call('incr', key)\n" + // 计数+1 + "if tonumber(current) == 1 then\n" + // 如果是第一次计数 + " redis.call('expire', key, time)\n" + // 设置过期时间(时间窗口) + "end\n" + + "return tonumber(current);"; // 返回当前计数(未限流) + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/ResourcesConfig.java b/huacai-framework/src/main/java/com/huacai/framework/config/ResourcesConfig.java new file mode 100644 index 0000000..4f83164 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/ResourcesConfig.java @@ -0,0 +1,89 @@ +package com.huacai.framework.config; + +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import com.huacai.common.config.HuaCaiConfig; +import com.huacai.common.constant.Constants; +import com.huacai.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 通用资源配置类 + * 用于配置静态资源映射、拦截器和跨域请求处理 + * + * @author huacai + */ +// 标记为Spring配置类 +@Configuration +// 实现WebMvcConfigurer接口,自定义Spring MVC配置 +public class ResourcesConfig implements WebMvcConfigurer +{ + // 注入重复提交拦截器(用于防止表单重复提交) + @Autowired + private RepeatSubmitInterceptor repeatSubmitInterceptor; + + /** + * 配置静态资源处理器 + * 用于映射静态资源路径到实际存储位置 + */ + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) + { + /** 本地文件上传路径映射 */ + // 映射以Constants.RESOURCE_PREFIX开头的URL到本地文件系统的上传目录 + registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**") + .addResourceLocations("file:" + HuaCaiConfig.getProfile() + "/"); + + /** Swagger UI资源映射 */ + // 映射Swagger UI的URL到META-INF中的资源目录 + registry.addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/") + .setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic()); // 设置缓存控制(5小时) + } + + /** + * 注册自定义拦截器 + * 用于添加重复提交拦截器等自定义拦截逻辑 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) + { + // 注册重复提交拦截器,拦截所有请求 + registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); + } + + /** + * 配置跨域过滤器 + * 用于处理不同域之间的请求访问限制 + */ + @Bean + public CorsFilter corsFilter() + { + // 创建跨域配置对象 + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); // 允许携带cookie + // 设置允许访问的源(*表示允许所有域,实际生产环境应指定具体域名) + config.addAllowedOriginPattern("*"); + // 设置允许的请求头(*表示允许所有头) + config.addAllowedHeader("*"); + // 设置允许的请求方法(*表示允许所有方法:GET/POST/PUT等) + config.addAllowedMethod("*"); + // 设置预检请求的有效期(1800秒,避免频繁预检) + config.setMaxAge(1800L); + + // 创建路径映射源,注册跨域配置 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); // 对所有路径生效 + + // 返回跨域过滤器 + return new CorsFilter(source); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/SecurityConfig.java b/huacai-framework/src/main/java/com/huacai/framework/config/SecurityConfig.java new file mode 100644 index 0000000..70ef4ea --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/SecurityConfig.java @@ -0,0 +1,148 @@ +package com.huacai.framework.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.filter.CorsFilter; +import com.huacai.framework.config.properties.PermitAllUrlProperties; +import com.huacai.framework.security.filter.JwtAuthenticationTokenFilter; +import com.huacai.framework.security.handle.AuthenticationEntryPointImpl; +import com.huacai.framework.security.handle.LogoutSuccessHandlerImpl; + +/** + * spring security配置 + * + * @author huacai + */ +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter +{ + /** + * 自定义用户认证逻辑 + */ + @Autowired + private UserDetailsService userDetailsService; + + /** + * 认证失败处理类 + */ + @Autowired + private AuthenticationEntryPointImpl unauthorizedHandler; + + /** + * 退出处理类 + */ + @Autowired + private LogoutSuccessHandlerImpl logoutSuccessHandler; + + /** + * token认证过滤器 + */ + @Autowired + private JwtAuthenticationTokenFilter authenticationTokenFilter; + + /** + * 跨域过滤器 + */ + @Autowired + private CorsFilter corsFilter; + + /** + * 允许匿名访问的地址 + */ + @Autowired + private PermitAllUrlProperties permitAllUrl; + + /** + * 解决 无法直接注入 AuthenticationManager + * + * @return + * @throws Exception + */ + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception + { + return super.authenticationManagerBean(); + } + + /** + * anyRequest | 匹配所有请求路径 + * access | SpringEl表达式结果为true时可以访问 + * anonymous | 匿名可以访问 + * denyAll | 用户不能访问 + * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) + * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 + * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 + * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 + * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 + * hasRole | 如果有参数,参数表示角色,则其角色可以访问 + * permitAll | 用户可以任意访问 + * rememberMe | 允许通过remember-me登录的用户访问 + * authenticated | 用户登录后可访问 + */ + @Override + protected void configure(HttpSecurity httpSecurity) throws Exception + { + // 注解标记允许匿名访问的url + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests(); + permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll()); + + httpSecurity + // CSRF禁用,因为不使用session + .csrf().disable() + // 禁用HTTP响应标头 + .headers().cacheControl().disable().and() + // 认证失败处理类 + .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() + // 基于token,所以不需要session + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + // 过滤请求 + .authorizeRequests() + // 对于登录login 注册register 验证码captchaImage 允许匿名访问 + .antMatchers("/login", "/register", "/captchaImage").permitAll() + // 静态资源,可匿名访问 + .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() + .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() + // 除上面外的所有请求全部需要鉴权认证 + .anyRequest().authenticated() + .and() + .headers().frameOptions().disable(); + // 添加Logout filter + httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); + // 添加JWT filter + httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); + // 添加CORS filter + httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); + httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); + } + + /** + * 强散列哈希加密实现 + */ + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() + { + return new BCryptPasswordEncoder(); + } + + /** + * 身份认证接口 + */ + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception + { + auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/ServerConfig.java b/huacai-framework/src/main/java/com/huacai/framework/config/ServerConfig.java new file mode 100644 index 0000000..95b6c42 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/ServerConfig.java @@ -0,0 +1,46 @@ +package com.huacai.framework.config; + +import javax.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import com.huacai.common.utils.ServletUtils; + +/** + * 服务相关配置类 + * 用于获取服务的基础信息(如完整请求路径等) + * + * @author huacai + */ +// 注册为Spring组件 +@Component +public class ServerConfig +{ + /** + * 获取完整的请求路径 + * 包括:域名,端口,上下文访问路径 + * + * @return 服务完整地址(如http://localhost:8080/huacai) + */ + public String getUrl() + { + // 获取当前请求对象 + HttpServletRequest request = ServletUtils.getRequest(); + // 生成并返回完整服务地址 + return getDomain(request); + } + + /** + * 从请求对象中提取服务域名及上下文路径 + * + * @param request HTTP请求对象 + * @return 服务基础地址(域名+端口+上下文路径) + */ + public static String getDomain(HttpServletRequest request) + { + // 获取请求的完整URL(如http://localhost:8080/huacai/user/list) + StringBuffer url = request.getRequestURL(); + // 获取上下文路径(如/huacai) + String contextPath = request.getServletContext().getContextPath(); + // 截取域名+端口部分,拼接上下文路径(结果如http://localhost:8080/huacai) + return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString(); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/ThreadPoolConfig.java b/huacai-framework/src/main/java/com/huacai/framework/config/ThreadPoolConfig.java new file mode 100644 index 0000000..4e0642a --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/ThreadPoolConfig.java @@ -0,0 +1,84 @@ +package com.huacai.framework.config; + +import com.huacai.common.utils.Threads; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 线程池配置类 + * 用于配置应用中使用的线程池,包括普通任务线程池和定时任务线程池 + * + * @author huacai + **/ +@Configuration +public class ThreadPoolConfig +{ + // 核心线程池大小:线程池维护的核心线程数量,即使线程空闲也不会被销毁 + private int corePoolSize = 50; + + // 最大可创建的线程数:线程池允许创建的最大线程数量,当核心线程都在工作且任务队列满时会创建新线程直到达到此值 + private int maxPoolSize = 200; + + // 队列最大长度:用于存放等待执行任务的阻塞队列容量 + private int queueCapacity = 1000; + + // 线程池维护线程所允许的空闲时间:当线程数量超过核心线程数时,多余的空闲线程的存活时间(单位:秒) + private int keepAliveSeconds = 300; + + /** + * 创建普通任务线程池实例 + * 用于处理一般的异步任务 + * + * @return 配置好的ThreadPoolTaskExecutor实例 + */ + @Bean(name = "threadPoolTaskExecutor") + public ThreadPoolTaskExecutor threadPoolTaskExecutor() + { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setMaxPoolSize(maxPoolSize); // 设置最大线程数 + executor.setCorePoolSize(corePoolSize); // 设置核心线程数 + executor.setQueueCapacity(queueCapacity); // 设置任务队列容量 + executor.setKeepAliveSeconds(keepAliveSeconds); // 设置空闲线程存活时间 + // 线程池对拒绝任务(无线程可用且队列已满)的处理策略:使用调用者所在线程执行任务 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + return executor; + } + + /** + * 创建定时任务线程池 + * 专门用于执行周期性任务或定时任务 + * + * @return 配置好的ScheduledExecutorService实例 + */ + @Bean(name = "scheduledExecutorService") + protected ScheduledExecutorService scheduledExecutorService() + { + return new ScheduledThreadPoolExecutor( + corePoolSize, // 核心线程数 + // 线程工厂:设置线程名称格式为"schedule-pool-%d",且为守护线程 + new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), + // 拒绝策略:使用调用者所在线程执行任务 + new ThreadPoolExecutor.CallerRunsPolicy() + ) { + /** + * 任务执行后回调方法 + * 用于在任务执行完成后处理异常信息 + * + * @param r 执行的任务 + * @param t 任务执行过程中抛出的异常(无异常则为null) + */ + @Override + protected void afterExecute(Runnable r, Throwable t) + { + super.afterExecute(r, t); + // 打印任务执行过程中发生的异常 + Threads.printException(r, t); + } + }; + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/WebMvcConfig.java b/huacai-framework/src/main/java/com/huacai/framework/config/WebMvcConfig.java new file mode 100644 index 0000000..feeb7cd --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/WebMvcConfig.java @@ -0,0 +1,22 @@ +package com.huacai.framework.config; + +import com.huacai.framework.interceptor.DemoEnvironmentInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Autowired + private DemoEnvironmentInterceptor demoEnvironmentInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 演示环境拦截器 + registry.addInterceptor(demoEnvironmentInterceptor) + .addPathPatterns("/**") // 拦截所有路径 + .excludePathPatterns("/login", "/logout", "/captchaImage"); // 排除登录等必要接口 + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/properties/DruidProperties.java b/huacai-framework/src/main/java/com/huacai/framework/config/properties/DruidProperties.java new file mode 100644 index 0000000..ceb2e57 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/properties/DruidProperties.java @@ -0,0 +1,89 @@ +package com.huacai.framework.config.properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import com.alibaba.druid.pool.DruidDataSource; + +/** + * druid 配置属性 + * + * @author huacai + */ +@Configuration +public class DruidProperties +{ + @Value("${spring.datasource.druid.initialSize}") + private int initialSize; + + @Value("${spring.datasource.druid.minIdle}") + private int minIdle; + + @Value("${spring.datasource.druid.maxActive}") + private int maxActive; + + @Value("${spring.datasource.druid.maxWait}") + private int maxWait; + + @Value("${spring.datasource.druid.connectTimeout}") + private int connectTimeout; + + @Value("${spring.datasource.druid.socketTimeout}") + private int socketTimeout; + + @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") + private int timeBetweenEvictionRunsMillis; + + @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") + private int minEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}") + private int maxEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.validationQuery}") + private String validationQuery; + + @Value("${spring.datasource.druid.testWhileIdle}") + private boolean testWhileIdle; + + @Value("${spring.datasource.druid.testOnBorrow}") + private boolean testOnBorrow; + + @Value("${spring.datasource.druid.testOnReturn}") + private boolean testOnReturn; + + public DruidDataSource dataSource(DruidDataSource datasource) + { + /** 配置初始化大小、最小、最大 */ + datasource.setInitialSize(initialSize); + datasource.setMaxActive(maxActive); + datasource.setMinIdle(minIdle); + + /** 配置获取连接等待超时的时间 */ + datasource.setMaxWait(maxWait); + + /** 配置驱动连接超时时间,检测数据库建立连接的超时时间,单位是毫秒 */ + datasource.setConnectTimeout(connectTimeout); + + /** 配置网络超时时间,等待数据库操作完成的网络超时时间,单位是毫秒 */ + datasource.setSocketTimeout(socketTimeout); + + /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */ + datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + + /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */ + datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); + + /** + * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 + */ + datasource.setValidationQuery(validationQuery); + /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */ + datasource.setTestWhileIdle(testWhileIdle); + /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnBorrow(testOnBorrow); + /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnReturn(testOnReturn); + return datasource; + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/config/properties/PermitAllUrlProperties.java b/huacai-framework/src/main/java/com/huacai/framework/config/properties/PermitAllUrlProperties.java new file mode 100644 index 0000000..19693de --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/config/properties/PermitAllUrlProperties.java @@ -0,0 +1,73 @@ +package com.huacai.framework.config.properties; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; +import org.apache.commons.lang3.RegExUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import com.huacai.common.annotation.Anonymous; + +/** + * 设置Anonymous注解允许匿名访问的url + * + * @author huacai + */ +@Configuration +public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware +{ + private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); + + private ApplicationContext applicationContext; + + private List urls = new ArrayList<>(); + + public String ASTERISK = "*"; + + @Override + public void afterPropertiesSet() + { + RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); + Map map = mapping.getHandlerMethods(); + + map.keySet().forEach(info -> { + HandlerMethod handlerMethod = map.get(info); + + // 获取方法上边的注解 替代path variable 为 * + Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class); + Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns()) + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); + + // 获取类上边的注解, 替代path variable 为 * + Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class); + Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns()) + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); + }); + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException + { + this.applicationContext = context; + } + + public List getUrls() + { + return urls; + } + + public void setUrls(List urls) + { + this.urls = urls; + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/datasource/DynamicDataSource.java b/huacai-framework/src/main/java/com/huacai/framework/datasource/DynamicDataSource.java new file mode 100644 index 0000000..94e10df --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/datasource/DynamicDataSource.java @@ -0,0 +1,44 @@ +package com.huacai.framework.datasource; + +import java.util.Map; +import javax.sql.DataSource; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * 动态数据源 + * 继承Spring AbstractRoutingDataSource,实现多数据源的动态切换功能 + * 可根据当前线程上下文 上下文选择不同的数据源进行操作 + * + * @author huacai + */ +public class DynamicDataSource extends AbstractRoutingDataSource +{ + /** + * 构造方法,初始化动态数据源 + * + * @param defaultTargetDataSource 默认数据源(当未指定数据源时使用) + * @param targetDataSources 目标数据源集合(key为数据源标识,value为对应的数据源实例) + */ + public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) + { + // 设置默认目标数据源 + super.setDefaultTargetDataSource(defaultTargetDataSource); + // 设置目标数据源映射关系 + super.setTargetDataSources(targetDataSources); + // 初始化数据源,使配置生效 + super.afterPropertiesSet(); + } + + /** + * 确定当前使用的数据源标识 + * 该方法返回的key将用于从targetDataSources中查找对应的数据源 + * + * @return 当前数据源的标识(从动态数据源上下文持有者中获取) + */ + @Override + protected Object determineCurrentLookupKey() + { + // 从上下文持有者中获取当前线程绑定的数据源类型 + return DynamicDataSourceContextHolder.getDataSourceType(); + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/datasource/DynamicDataSourceContextHolder.java b/huacai-framework/src/main/java/com/huacai/framework/datasource/DynamicDataSourceContextHolder.java new file mode 100644 index 0000000..d0b0d5f --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/datasource/DynamicDataSourceContextHolder.java @@ -0,0 +1,45 @@ +package com.huacai.framework.datasource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 数据源切换处理 + * + * @author huacai + */ +public class DynamicDataSourceContextHolder +{ + public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); + + /** + * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, + * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 + */ + private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * 设置数据源的变量 + */ + public static void setDataSourceType(String dsType) + { + log.info("切换到{}数据源", dsType); + CONTEXT_HOLDER.set(dsType); + } + + /** + * 获得数据源的变量 + */ + public static String getDataSourceType() + { + return CONTEXT_HOLDER.get(); + } + + /** + * 清空数据源变量 + */ + public static void clearDataSourceType() + { + CONTEXT_HOLDER.remove(); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/interceptor/DemoEnvironmentInterceptor.java b/huacai-framework/src/main/java/com/huacai/framework/interceptor/DemoEnvironmentInterceptor.java new file mode 100644 index 0000000..d5abac9 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/interceptor/DemoEnvironmentInterceptor.java @@ -0,0 +1,60 @@ +package com.huacai.framework.interceptor; + +import com.huacai.common.exception.DemoEnvironmentException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 演示环境拦截器 + * 用于在演示环境中限制非GET请求,防止数据被修改 + */ +// 注册为Spring组件 +@Component +public class DemoEnvironmentInterceptor implements HandlerInterceptor { + + // 从配置文件中读取是否启用演示环境(默认不启用) + @Value("${demo.env.enabled:false}") + private boolean isDemoEnvironment; + + // 需要排除的URL(这些URL即使在演示环境也允许所有请求) + private static final String[] EXCLUDE_URLS = { + "/login", // 登录 + "/logout", // 注销 + "/captchaImage" // 验证码 + }; + + /** + * 请求处理前拦截 + * 用于判断是否为演示环境,并限制非GET请求 + */ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // 如果不是演示环境,直接放行 + if (!isDemoEnvironment) { + return true; + } + + // 获取当前请求的URI + String requestURI = request.getRequestURI(); + + // 检查请求URI是否在排除列表中,如果是则放行 + for (String excludeUrl : EXCLUDE_URLS) { + if (requestURI.contains(excludeUrl)) { + return true; + } + } + + // 拦截所有非GET请求(演示环境不允许修改操作) + String method = request.getMethod(); + if (!"GET".equalsIgnoreCase(method)) { + throw new DemoEnvironmentException("演示环境,不允许进行此操作"); + } + + // GET请求放行 + return true; + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/interceptor/RepeatSubmitInterceptor.java b/huacai-framework/src/main/java/com/huacai/framework/interceptor/RepeatSubmitInterceptor.java new file mode 100644 index 0000000..82c7ad8 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/interceptor/RepeatSubmitInterceptor.java @@ -0,0 +1,77 @@ +package com.huacai.framework.interceptor; + +import java.lang.reflect.Method; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import com.alibaba.fastjson2.JSON; +import com.huacai.common.annotation.RepeatSubmit; +import com.huacai.common.core.domain.AjaxResult; +import com.huacai.common.utils.ServletUtils; + +/** + * 防止重复提交拦截器 + * 抽象类,定义防止重复提交的拦截逻辑,具体验证规则由子类实现 + * + * @author huacai + */ +@Component +public abstract class RepeatSubmitInterceptor implements HandlerInterceptor +{ + /** + * 请求处理前的拦截方法 + * 用于判断当前请求是否为重复提交,若为重复提交则返回错误响应 + * + * @param request HTTP请求对象 + * @param response HTTP响应对象 + * @param handler 处理器对象(当前请求对应的Controller方法) + * @return true-继续处理请求;false-中断请求处理 + * @throws Exception 处理过程中发生异常时抛出 + */ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception + { + // 判断处理器是否为HandlerMethod(即Controller中的方法) + if (handler instanceof HandlerMethod) + { + HandlerMethod handlerMethod = (HandlerMethod) handler; + // 获取当前请求对应的方法对象 + Method method = handlerMethod.getMethod(); + // 检查方法上是否标注了@RepeatSubmit注解 + RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); + if (annotation != null) + { + // 调用子类实现的方法判断是否为重复提交 + if (this.isRepeatSubmit(request, annotation)) + { + // 若为重复提交,构建错误响应对象 + AjaxResult ajaxResult = AjaxResult.error(annotation.message()); + // 将错误响应转换为JSON并写入响应流 + ServletUtils.renderString(response, JSON.toJSONString(ajaxResult)); + // 返回false中断请求处理 + return false; + } + } + // 非重复提交,继续处理请求 + return true; + } + else + { + // 非Controller方法请求,直接放行 + return true; + } + } + + /** + * 验证是否重复提交(抽象方法) + * 由子类实现具体的防重复提交规则(如基于参数、令牌、时间等) + * + * @param request 请求信息 + * @param annotation 防重复提交注解,包含自定义消息和间隔时间等参数 + * @return true-是重复提交;false-不是重复提交 + * @throws Exception 验证过程中发生异常时抛出 + */ + public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation); +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/interceptor/impl/SameUrlDataInterceptor.java b/huacai-framework/src/main/java/com/huacai/framework/interceptor/impl/SameUrlDataInterceptor.java new file mode 100644 index 0000000..fff68fd --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/interceptor/impl/SameUrlDataInterceptor.java @@ -0,0 +1,145 @@ +package com.huacai.framework.interceptor.impl; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.huacai.common.annotation.RepeatSubmit; +import com.huacai.common.constant.CacheConstants; +import com.huacai.common.core.redis.RedisCache; +import com.huacai.common.filter.RepeatedlyRequestWrapper; +import com.huacai.common.utils.StringUtils; +import com.huacai.common.utils.http.HttpHelper; +import com.huacai.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 重复提交拦截器实现类(基于URL和请求数据判断) + * 判断请求的URL和数据是否与上一次相同,若相同则视为重复提交表单(默认有效时间10秒内) + * + * @author huacai + */ +@Component +public class SameUrlDataInterceptor extends RepeatSubmitInterceptor +{ + // 用于存储请求参数的Map键名 + public final String REPEAT_PARAMS = "repeatParams"; + + // 用于存储请求时间的Map键名 + public final String REPEAT_TIME = "repeatTime"; + + // 从配置文件中获取令牌的请求头自定义标识(如Token的Header名称) + @Value("${token.header}") + private String header; + + // 注入Redis缓存工具,用于存储请求记录 + @Autowired + private RedisCache redisCache; + + /** + * 判断是否为重复提交 + * + * @param request 当前HTTP请求对象 + * @param annotation 重复提交注解,包含间隔时间等配置 + * @return true-是重复提交;false-不是重复提交 + */ + @SuppressWarnings("unchecked") + @Override + public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) + { + // 存储当前请求的参数 + String nowParams = ""; + // 如果请求是可重复读取的包装类(解决请求体只能读一次的问题) + if (request instanceof RepeatedlyRequestWrapper) + { + RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; + // 获取请求体中的参数 + nowParams = HttpHelper.getBodyString(repeatedlyRequest); + } + + // 如果body参数为空,则获取URL参数(如GET请求的query参数) + if (StringUtils.isEmpty(nowParams)) + { + nowParams = JSON.toJSONString(request.getParameterMap()); + } + + // 存储当前请求的数据(参数和时间) + Map nowDataMap = new HashMap(); + nowDataMap.put(REPEAT_PARAMS, nowParams); // 当前请求参数 + nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); // 当前请求时间戳 + + // 获取请求的URL(作为缓存的一部分key) + String url = request.getRequestURI(); + + // 从请求头中获取令牌(作为唯一标识,若为空则用空字符串) + String submitKey = StringUtils.trimToEmpty(request.getHeader(header)); + + // 构建Redis缓存的key:前缀 + URL + 令牌(确保同一用户同一URL的唯一性) + String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey; + + // 从Redis中获取之前的请求记录 + Object sessionObj = redisCache.getCacheObject(cacheRepeatKey); + if (sessionObj != null) + { + // 将缓存中的数据转换为Map + Map sessionMap = (Map) sessionObj; + // 判断缓存中是否存在当前URL的请求记录 + if (sessionMap.containsKey(url)) + { + // 获取上一次请求的数据 + Map preDataMap = (Map) sessionMap.get(url); + // 比较参数是否相同且时间间隔是否小于注解配置的间隔时间 + if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) + { + // 满足条件,视为重复提交 + return true; + } + } + } + + // 若不是重复提交,则将当前请求数据存入Redis + Map cacheMap = new HashMap(); + cacheMap.put(url, nowDataMap); + // 设置缓存过期时间为注解配置的间隔时间(毫秒) + redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS); + // 不是重复提交 + return false; + } + + /** + * 比较两次请求的参数是否相同 + * + * @param nowMap 当前请求的数据Map + * @param preMap 上一次请求的数据Map + * @return true-参数相同;false-参数不同 + */ + private boolean compareParams(Map nowMap, Map preMap) + { + String nowParams = (String) nowMap.get(REPEAT_PARAMS); + String preParams = (String) preMap.get(REPEAT_PARAMS); + return nowParams.equals(preParams); + } + + /** + * 比较两次请求的时间间隔是否小于指定的间隔时间 + * + * @param nowMap 当前请求的数据Map + * @param preMap 上一次请求的数据Map + * @param interval 允许的间隔时间(毫秒) + * @return true-间隔时间小于指定值;false-间隔时间大于等于指定值 + */ + private boolean compareTime(Map nowMap, Map preMap, int interval) + { + long time1 = (Long) nowMap.get(REPEAT_TIME); + long time2 = (Long) preMap.get(REPEAT_TIME); + // 若当前时间与上一次时间的差值小于间隔时间,则视为重复提交 + if ((time1 - time2) < interval) + { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/manager/AsyncManager.java b/huacai-framework/src/main/java/com/huacai/framework/manager/AsyncManager.java new file mode 100644 index 0000000..3e947ab --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/manager/AsyncManager.java @@ -0,0 +1,70 @@ +package com.huacai.framework.manager; + +import java.util.TimerTask; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import com.huacai.common.utils.Threads; +import com.huacai.common.utils.spring.SpringUtils; + +/** + * 异步任务管理器 + * 负责管理异步任务的调度执行,基于单例模式提供统一的异步任务处理入口 + * + * @author huacai + */ +public class AsyncManager +{ + /** + * 操作延迟时间(10毫秒) + * 表示提交的异步任务将在延迟10毫秒后执行 + */ + private final int OPERATE_DELAY_TIME = 10; + + /** + * 异步操作任务调度线程池 + * 通过Spring工具类获取配置好的定时任务线程池实例(对应bean名称:scheduledExecutorService) + */ + private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); + + /** + * 单例模式实现 - 私有构造方法 + * 防止外部通过new创建实例,保证全局唯一 + */ + private AsyncManager(){} + + /** + * 单例实例对象 + * 类加载时初始化,保证线程安全 + */ + private static AsyncManager me = new AsyncManager(); + + /** + * 获取单例实例 + * + * @return 异步任务管理器的唯一实例 + */ + public static AsyncManager me() + { + return me; + } + + /** + * 执行异步任务 + * 将任务提交到调度线程池,延迟指定时间(OPERATE_DELAY_TIME)后执行 + * + * @param task 待执行的定时任务(TimerTask类型) + */ + public void execute(TimerTask task) + { + executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); + } + + /** + * 停止任务线程池 + * 调用线程工具类的方法,优雅关闭线程池并等待其终止 + */ + public void shutdown() + { + Threads.shutdownAndAwaitTermination(executor); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/manager/ShutdownManager.java b/huacai-framework/src/main/java/com/huacai/framework/manager/ShutdownManager.java new file mode 100644 index 0000000..2cd1f95 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/manager/ShutdownManager.java @@ -0,0 +1,50 @@ +package com.huacai.framework.manager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import javax.annotation.PreDestroy; + +/** + * 应用关闭管理器 + * 用于确保应用退出时确保后台线程(如异步任务线程池)被正确关闭,避免资源泄露 + * + * @author huacai + */ +@Component +public class ShutdownManager +{ + // 日志记录器,使用"sys-user"日志名 + private static final Logger logger = LoggerFactory.getLogger("sys-user"); + + /** + * 应用销毁前执行的方法 + * 标注@PreDestroy注解,在Spring容器销毁该Bean之前自动调用 + */ + @PreDestroy + public void destroy() + { + // 关闭异步任务管理器 + shutdownAsyncManager(); + } + + /** + * 停止异步执行任务的线程池 + * 调用AsyncManager的shutdown方法,优雅关闭线程池 + */ + private void shutdownAsyncManager() + { + try + { + // 记录关闭线程池的日志 + logger.info("====关闭后台任务任务线程池===="); + // 获取异步任务管理器单例并调用shutdown方法 + AsyncManager.me().shutdown(); + } + catch (Exception e) + { + // 记录关闭过程中发生的异常 + logger.error(e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/manager/factory/AsyncFactory.java b/huacai-framework/src/main/java/com/huacai/framework/manager/factory/AsyncFactory.java new file mode 100644 index 0000000..287eca3 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/manager/factory/AsyncFactory.java @@ -0,0 +1,102 @@ +package com.huacai.framework.manager.factory; + +import java.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.huacai.common.constant.Constants; +import com.huacai.common.utils.LogUtils; +import com.huacai.common.utils.ServletUtils; +import com.huacai.common.utils.StringUtils; +import com.huacai.common.utils.ip.AddressUtils; +import com.huacai.common.utils.ip.IpUtils; +import com.huacai.common.utils.spring.SpringUtils; +import com.huacai.system.domain.SysLogininfor; +import com.huacai.system.domain.SysOperLog; +import com.huacai.system.service.ISysLogininforService; +import com.huacai.system.service.ISysOperLogService; +import eu.bitwalker.useragentutils.UserAgent; + +/** + * 异步工厂(产生任务用) + * + * @author huacai + */ +public class AsyncFactory +{ + private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user"); + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param status 状态 + * @param message 消息 + * @param args 列表 + * @return 任务task + */ + public static TimerTask recordLogininfor(final String username, final String status, final String message, + final Object... args) + { + final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + final String ip = IpUtils.getIpAddr(); + return new TimerTask() + { + @Override + public void run() + { + String address = AddressUtils.getRealAddressByIP(ip); + StringBuilder s = new StringBuilder(); + s.append(LogUtils.getBlock(ip)); + s.append(address); + s.append(LogUtils.getBlock(username)); + s.append(LogUtils.getBlock(status)); + s.append(LogUtils.getBlock(message)); + // 打印信息到日志 + sys_user_logger.info(s.toString(), args); + // 获取客户端操作系统 + String os = userAgent.getOperatingSystem().getName(); + // 获取客户端浏览器 + String browser = userAgent.getBrowser().getName(); + // 封装对象 + SysLogininfor logininfor = new SysLogininfor(); + logininfor.setUserName(username); + logininfor.setIpaddr(ip); + logininfor.setLoginLocation(address); + logininfor.setBrowser(browser); + logininfor.setOs(os); + logininfor.setMsg(message); + // 日志状态 + if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) + { + logininfor.setStatus(Constants.SUCCESS); + } + else if (Constants.LOGIN_FAIL.equals(status)) + { + logininfor.setStatus(Constants.FAIL); + } + // 插入数据 + SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor); + } + }; + } + + /** + * 操作日志记录 + * + * @param operLog 操作日志信息 + * @return 任务task + */ + public static TimerTask recordOper(final SysOperLog operLog) + { + return new TimerTask() + { + @Override + public void run() + { + // 远程查询操作地点 + operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp())); + SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog); + } + }; + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/security/context/AuthenticationContextHolder.java b/huacai-framework/src/main/java/com/huacai/framework/security/context/AuthenticationContextHolder.java new file mode 100644 index 0000000..b9e06c1 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/security/context/AuthenticationContextHolder.java @@ -0,0 +1,46 @@ +package com.huacai.framework.security.context; + +import org.springframework.security.core.Authentication; + +/** + * 身份验证信息持有者 + * 用于在当前线程中存储和获取Spring Security的身份验证对象(Authentication) + * 提供线程隔离的身份信息管理,确保多线程环境下的安全性 + * + * @author huacai + */ +public class AuthenticationContextHolder +{ + // 线程本地变量,用于存储当前线程的Authentication对象 + // ThreadLocal确保每个线程只能访问自己的变量副本,实现线程隔离 + private static final ThreadLocal contextHolder = new ThreadLocal<>(); + + /** + * 获取当前线程中的身份验证对象 + * + * @return 当前线程的Authentication对象,若不存在则返回null + */ + public static Authentication getContext() + { + return contextHolder.get(); + } + + /** + * 设置当前线程的身份验证对象 + * + * @param context 要存储的Authentication对象 + */ + public static void setContext(Authentication context) + { + contextHolder.set(context); + } + + /** + * 清除当前线程中的身份验证对象 + * 通常在请求处理完成后调用,避免线程复用导致的信息泄露 + */ + public static void clearContext() + { + contextHolder.remove(); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/security/context/PermissionContextHolder.java b/huacai-framework/src/main/java/com/huacai/framework/security/context/PermissionContextHolder.java new file mode 100644 index 0000000..1035177 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/security/context/PermissionContextHolder.java @@ -0,0 +1,43 @@ +package com.huacai.framework.security.context; + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import com.huacai.common.core.text.Convert; + +/** + * 权限信息持有者 + * 用于在当前请求范围内存储和获取权限相关信息,基于请求上下文实现 + * + * @author huacai + */ +public class PermissionContextHolder +{ + // 权限上下文在请求属性中的存储键名 + private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT"; + + /** + * 设置当前请求的权限信息 + * 将权限字符串存入当前请求的属性中,作用域为当前请求 + * + * @param permission 要存储的权限信息字符串 + */ + public static void setContext(String permission) + { + // 获取当前请求的属性对象,将权限信息存入,作用域为REQUEST(仅当前请求有效) + RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission, + RequestAttributes.SCOPE_REQUEST); + } + + /** + * 获取当前请求的权限信息 + * 从当前请求的属性中获取之前存储的权限信息,转换为字符串 + * + * @return 当前请求的权限信息字符串,若不存在则返回空字符串 + */ + public static String getContext() + { + // 从当前请求属性中获取权限信息,转换为字符串后返回 + return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES, + RequestAttributes.SCOPE_REQUEST)); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/security/filter/JwtAuthenticationTokenFilter.java b/huacai-framework/src/main/java/com/huacai/framework/security/filter/JwtAuthenticationTokenFilter.java new file mode 100644 index 0000000..ce7f95c --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/security/filter/JwtAuthenticationTokenFilter.java @@ -0,0 +1,66 @@ +package com.huacai.framework.security.filter; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import com.huacai.common.core.domain.model.LoginUser; +import com.huacai.common.utils.SecurityUtils; +import com.huacai.common.utils.StringUtils; +import com.huacai.framework.web.service.TokenService; + +/** + * JWT身份验证令牌过滤器 + * 用于验证请求中的JWT令牌有效性,并将解析后的用户信息存入Spring Security上下文 + * 确保每个请求只被过滤一次(继承OncePerRequestFilter) + * + * @author huacai + */ +@Component +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter +{ + // 注入令牌服务,用于处理令牌的解析、验证等操作 + @Autowired + private TokenService tokenService; + + /** + * 过滤器核心方法,处理每个请求 + * + * @param request 当前HTTP请求对象 + * @param response 当前HTTP响应对象 + * @param chain 过滤器链,用于传递请求到下一个过滤器 + * @throws ServletException 处理请求时发生的Servlet异常 + * @throws IOException 处理请求时发生的IO异常 + */ + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException + { + // 从请求中获取登录用户信息(通过TokenService解析请求中的令牌) + LoginUser loginUser = tokenService.getLoginUser(request); + + // 若登录用户信息存在,且当前Security上下文没有认证信息 + if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) + { + // 验证令牌的有效性(如是否过期、是否被篡改等) + tokenService.verifyToken(loginUser); + + // 创建认证令牌对象,包含用户信息和权限集合 + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); + // 设置认证详情(如请求IP、会话ID等) + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + // 将认证信息存入Security上下文,供后续权限校验使用 + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + + // 继续执行过滤器链,将请求传递给下一个过滤器或目标资源 + chain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/security/handle/AuthenticationEntryPointImpl.java b/huacai-framework/src/main/java/com/huacai/framework/security/handle/AuthenticationEntryPointImpl.java new file mode 100644 index 0000000..f302e22 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/security/handle/AuthenticationEntryPointImpl.java @@ -0,0 +1,48 @@ +package com.huacai.framework.security.handle; + +import java.io.IOException; +import java.io.Serializable; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.huacai.common.constant.HttpStatus; +import com.huacai.common.core.domain.AjaxResult; +import com.huacai.common.utils.ServletUtils; +import com.huacai.common.utils.StringUtils; + +/** + * 认证失败处理类 + * 实现Spring Security的AuthenticationEntryPoint接口,用于处理未认证请求(返回未授权响应) + * + * @author huacai + */ +@Component +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable +{ + // 序列化版本号,用于对象序列化时的版本控制 + private static final long serialVersionUID = -8970718410437077606L; + + /** + * 处理认证失败的请求 + * 当用户未认证(如未登录、令牌无效等)访问受保护资源时,该方法被调用 + * + * @param request 当前HTTP请求对象 + * @param response 当前HTTP响应对象 + * @param e 认证异常(包含认证失败的原因) + * @throws IOException 处理响应时发生的IO异常 + */ + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) + throws IOException + { + // 设置错误状态码为401(未授权) + int code = HttpStatus.UNAUTHORIZED; + // 构建错误消息,包含请求的URI和认证失败提示 + String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); + // 将错误信息封装为AjaxResult对象,转换为JSON并写入响应流 + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg))); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/security/handle/LogoutSuccessHandlerImpl.java b/huacai-framework/src/main/java/com/huacai/framework/security/handle/LogoutSuccessHandlerImpl.java new file mode 100644 index 0000000..29cfa91 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/security/handle/LogoutSuccessHandlerImpl.java @@ -0,0 +1,62 @@ +package com.huacai.framework.security.handle; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import com.alibaba.fastjson2.JSON; +import com.huacai.common.constant.Constants; +import com.huacai.common.core.domain.AjaxResult; +import com.huacai.common.core.domain.model.LoginUser; +import com.huacai.common.utils.MessageUtils; +import com.huacai.common.utils.ServletUtils; +import com.huacai.common.utils.StringUtils; +import com.huacai.framework.manager.AsyncManager; +import com.huacai.framework.manager.factory.AsyncFactory; +import com.huacai.framework.web.service.TokenService; + +/** + * 自定义退出处理类 + * 实现Spring Security的LogoutSuccessHandler接口,用于处理用户退出登录成功后的逻辑 + * + * @author huacai + */ +@Configuration +public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler +{ + // 注入令牌服务,用于处理登录用户的令牌信息 + @Autowired + private TokenService tokenService; + + /** + * 退出登录成功后的处理方法 + * + * @param request HTTP请求对象 + * @param response HTTP响应对象 + * @param authentication 认证信息对象(包含当前登录用户信息) + * @throws IOException 处理响应时发生的IO异常 + * @throws ServletException 处理请求时发生的Servlet异常 + */ + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException + { + // 从请求中获取当前登录用户信息 + LoginUser loginUser = tokenService.getLoginUser(request); + // 若登录用户信息存在 + if (StringUtils.isNotNull(loginUser)) + { + String userName = loginUser.getUsername(); + // 从缓存中删除该用户的登录记录(使令牌失效) + tokenService.delLoginUser(loginUser.getToken()); + // 异步记录用户退出日志(使用异步管理器执行日志记录任务) + AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success"))); + } + // 向客户端返回退出成功的响应(JSON格式的成功消息) + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success")))); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/domain/Server.java b/huacai-framework/src/main/java/com/huacai/framework/web/domain/Server.java new file mode 100644 index 0000000..ff7e48b --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/domain/Server.java @@ -0,0 +1,302 @@ +package com.huacai.framework.web.domain; + +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import com.huacai.common.utils.Arith; +import com.huacai.common.utils.ip.IpUtils; +import com.huacai.framework.web.domain.server.Cpu; +import com.huacai.framework.web.domain.server.Jvm; +import com.huacai.framework.web.domain.server.Mem; +import com.huacai.framework.web.domain.server.Sys; +import com.huacai.framework.web.domain.server.SysFile; +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.CentralProcessor.TickType; +import oshi.hardware.GlobalMemory; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.software.os.FileSystem; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.Util; + +/** + * 服务器相关信息实体类 + * 用于封装服务器的CPU、内存、JVM、磁盘等信息 + * + * @author huacai + */ +public class Server +{ + // OSHI库获取硬件信息时的等待时间(毫秒) + private static final int OSHI_WAIT_SECOND = 1000; + + /** + * CPU相关信息 + */ + private Cpu cpu = new Cpu(); + + /** + * 内存相关信息 + */ + private Mem mem = new Mem(); + + /** + * JVM相关信息 + */ + private Jvm jvm = new Jvm(); + + /** + * 服务器相关信息 + */ + private Sys sys = new Sys(); + + /** + * 磁盘相关信息列表 + */ + private List sysFiles = new LinkedList(); + + // 获取CPU信息 + public Cpu getCpu() + { + return cpu; + } + + // 设置CPU信息 + public void setCpu(Cpu cpu) + { + this.cpu = cpu; + } + + // 获取内存信息 + public Mem getMem() + { + return mem; + } + + // 设置内存信息 + public void setMem(Mem mem) + { + this.mem = mem; + } + + // 获取JVM信息 + public Jvm getJvm() + { + return jvm; + } + + // 设置JVM信息 + public void setJvm(Jvm jvm) + { + this.jvm = jvm; + } + + // 获取服务器信息 + public Sys getSys() + { + return sys; + } + + // 设置服务器信息 + public void setSys(Sys sys) + { + this.sys = sys; + } + + // 获取磁盘信息列表 + public List getSysFiles() + { + return sysFiles; + } + + // 设置磁盘信息列表 + public void setSysFiles(List sysFiles) + { + this.sysFiles = sysFiles; + } + + /** + * 从系统中获取并填充服务器所有信息 + * 使用OSHI库获取硬件信息 + * + * @throws Exception 可能的异常(如硬件信息获取失败) + */ + public void copyTo() throws Exception + { + SystemInfo si = new SystemInfo(); // OSHI系统信息入口 + HardwareAbstractionLayer hal = si.getHardware(); // 硬件抽象层 + + // 设置CPU信息 + setCpuInfo(hal.getProcessor()); + + // 设置内存信息 + setMemInfo(hal.getMemory()); + + // 设置服务器基础信息 + setSysInfo(); + + // 设置JVM信息 + setJvmInfo(); + + // 设置磁盘信息 + setSysFiles(si.getOperatingSystem()); + } + + /** + * 设置CPU信息 + * + * @param processor OSHI的CPU处理器对象 + */ + private void setCpuInfo(CentralProcessor processor) + { + // 获取CPU负载滴答数(第一次采样) + long[] prevTicks = processor.getSystemCpuLoadTicks(); + // 等待一段时间(让CPU负载有变化) + Util.sleep(OSHI_WAIT_SECOND); + // 获取CPU负载滴答数(第二次采样) + long[] ticks = processor.getSystemCpuLoadTicks(); + + // 计算各状态的CPU时间(两次采样的差值) + long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; // 低优先级用户态时间 + long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; // 硬中断时间 + long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; // 软中断时间 + long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; // 被其他虚拟机占用的时间 + long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; // 系统态时间 + long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; // 用户态时间 + long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; // IO等待时间 + long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; // 空闲时间 + + // 总CPU时间 + long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; + + // 设置CPU核心数 + cpu.setCpuNum(processor.getLogicalProcessorCount()); + // 设置总CPU时间(用于计算使用率) + cpu.setTotal(totalCpu); + // 设置系统态时间 + cpu.setSys(cSys); + // 设置用户态时间 + cpu.setUsed(user); + // 设置IO等待时间 + cpu.setWait(iowait); + // 设置空闲时间 + cpu.setFree(idle); + } + + /** + * 设置内存信息 + * + * @param memory OSHI的内存对象 + */ + private void setMemInfo(GlobalMemory memory) + { + // 设置总内存(字节) + mem.setTotal(memory.getTotal()); + // 设置已使用内存(总内存 - 可用内存) + mem.setUsed(memory.getTotal() - memory.getAvailable()); + // 设置空闲内存 + mem.setFree(memory.getAvailable()); + } + + /** + * 设置服务器基础信息 + */ + private void setSysInfo() + { + Properties props = System.getProperties(); // 系统属性 + // 设置服务器名称(主机名) + sys.setComputerName(IpUtils.getHostName()); + // 设置服务器IP地址 + sys.setComputerIp(IpUtils.getHostIp()); + // 设置操作系统名称 + sys.setOsName(props.getProperty("os.name")); + // 设置系统架构 + sys.setOsArch(props.getProperty("os.arch")); + // 设置项目部署路径 + sys.setUserDir(props.getProperty("user.dir")); + } + + /** + * 设置JVM信息 + * + * @throws UnknownHostException 可能的主机信息获取异常 + */ + private void setJvmInfo() throws UnknownHostException + { + Properties props = System.getProperties(); + // 设置JVM总内存(字节) + jvm.setTotal(Runtime.getRuntime().totalMemory()); + // 设置JVM最大内存(字节) + jvm.setMax(Runtime.getRuntime().maxMemory()); + // 设置JVM空闲内存(字节) + jvm.setFree(Runtime.getRuntime().freeMemory()); + // 设置JDK版本 + jvm.setVersion(props.getProperty("java.version")); + // 设置JDK安装路径 + jvm.setHome(props.getProperty("java.home")); + } + + /** + * 设置磁盘信息 + * + * @param os OSHI的操作系统对象 + */ + private void setSysFiles(OperatingSystem os) + { + FileSystem fileSystem = os.getFileSystem(); // 文件系统 + List fsArray = fileSystem.getFileStores(); // 磁盘分区列表 + + // 遍历所有磁盘分区,封装信息 + for (OSFileStore fs : fsArray) + { + long free = fs.getUsableSpace(); // 可用空间(字节) + long total = fs.getTotalSpace(); // 总空间(字节) + long used = total - free; // 已使用空间 + + SysFile sysFile = new SysFile(); + sysFile.setDirName(fs.getMount()); // 挂载点路径 + sysFile.setSysTypeName(fs.getType()); // 文件系统类型 + sysFile.setTypeName(fs.getName()); // 分区名称 + sysFile.setTotal(convertFileSize(total)); // 总大小(格式化) + sysFile.setFree(convertFileSize(free)); // 剩余大小(格式化) + sysFile.setUsed(convertFileSize(used)); // 已使用大小(格式化) + // 计算使用率(保留两位小数) + sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100)); + sysFiles.add(sysFile); + } + } + + /** + * 字节大小转换为易读格式(如B、KB、MB、GB) + * + * @param size 字节大小 + * @return 转换后的字符串(如1.5 GB) + */ + public String convertFileSize(long size) + { + long kb = 1024; + long mb = kb * 1024; + long gb = mb * 1024; + + if (size >= gb) + { + return String.format("%.1f GB", (float) size / gb); + } + else if (size >= mb) + { + float f = (float) size / mb; + return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f); // 大于100MB时取整 + } + else if (size >= kb) + { + float f = (float) size / kb; + return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f); // 大于100KB时取整 + } + else + { + return String.format("%d B", size); + } + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Cpu.java b/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Cpu.java new file mode 100644 index 0000000..099f8d1 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Cpu.java @@ -0,0 +1,129 @@ +package com.huacai.framework.web.domain.server; + +import com.huacai.common.utils.Arith; + +/** + * CPU相关信息实体类 + * 用于封装CPU的核心数、使用率等信息 + * + * @author huacai + */ +public class Cpu +{ + /** + * 核心数 + */ + private int cpuNum; + + /** + * CPU总的使用率(原始值,未格式化) + */ + private double total; + + /** + * CPU系统使用率(原始值,未格式化) + */ + private double sys; + + /** + * CPU用户使用率(原始值,未格式化) + */ + private double used; + + /** + * CPU当前等待率(原始值,未格式化) + */ + private double wait; + + /** + * CPU当前空闲率(原始值,未格式化) + */ + private double free; + + // 获取CPU核心数 + public int getCpuNum() + { + return cpuNum; + } + + // 设置CPU核心数 + public void setCpuNum(int cpuNum) + { + this.cpuNum = cpuNum; + } + + /** + * 获取CPU总使用率(格式化后,百分比) + * 计算方式:原始总使用率 * 100,保留2位小数 + */ + public double getTotal() + { + return Arith.round(Arith.mul(total, 100), 2); + } + + // 设置CPU总使用率原始值 + public void setTotal(double total) + { + this.total = total; + } + + /** + * 获取CPU系统使用率(格式化后,百分比) + * 计算方式:(系统使用率 / 总使用率) * 100,保留2位小数 + */ + public double getSys() + { + return Arith.round(Arith.mul(sys / total, 100), 2); + } + + // 设置CPU系统使用率原始值 + public void setSys(double sys) + { + this.sys = sys; + } + + /** + * 获取CPU用户使用率(格式化后,百分比) + * 计算方式:(用户使用率 / 总使用率) * 100,保留2位小数 + */ + public double getUsed() + { + return Arith.round(Arith.mul(used / total, 100), 2); + } + + // 设置CPU用户使用率原始值 + public void setUsed(double used) + { + this.used = used; + } + + /** + * 获取CPU当前等待率(格式化后,百分比) + * 计算方式:(等待率 / 总使用率) * 100,保留2位小数 + */ + public double getWait() + { + return Arith.round(Arith.mul(wait / total, 100), 2); + } + + // 设置CPU当前等待率原始值 + public void setWait(double wait) + { + this.wait = wait; + } + + /** + * 获取CPU当前空闲率(格式化后,百分比) + * 计算方式:(空闲率 / 总使用率) * 100,保留2位小数 + */ + public double getFree() + { + return Arith.round(Arith.mul(free / total, 100), 2); + } + + // 设置CPU当前空闲率原始值 + public void setFree(double free) + { + this.free = free; + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Jvm.java b/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Jvm.java new file mode 100644 index 0000000..1078309 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Jvm.java @@ -0,0 +1,162 @@ +package com.huacai.framework.web.domain.server; + +import java.lang.management.ManagementFactory; +import com.huacai.common.utils.Arith; +import com.huacai.common.utils.DateUtils; + +/** + * JVM相关信息实体类 + * 用于封装JVM的内存使用、版本、运行时间等信息 + * + * @author huacai + */ +public class Jvm +{ + /** + * 当前JVM占用的内存总数(字节,原始值) + */ + private double total; + + /** + * JVM最大可用内存总数(字节,原始值) + */ + private double max; + + /** + * JVM空闲内存(字节,原始值) + */ + private double free; + + /** + * JDK版本 + */ + private String version; + + /** + * JDK安装路径 + */ + private String home; + + /** + * 获取当前JVM占用的内存总数(M) + * 计算方式:原始字节数 / (1024*1024),保留2位小数 + */ + public double getTotal() + { + return Arith.div(total, (1024 * 1024), 2); + } + + // 设置当前JVM占用的内存总数(原始字节数) + public void setTotal(double total) + { + this.total = total; + } + + /** + * 获取JVM最大可用内存总数(M) + * 计算方式:原始字节数 / (1024*1024),保留2位小数 + */ + public double getMax() + { + return Arith.div(max, (1024 * 1024), 2); + } + + // 设置JVM最大可用内存总数(原始字节数) + public void setMax(double max) + { + this.max = max; + } + + /** + * 获取JVM空闲内存(M) + * 计算方式:原始字节数 / (1024*1024),保留2位小数 + */ + public double getFree() + { + return Arith.div(free, (1024 * 1024), 2); + } + + // 设置JVM空闲内存(原始字节数) + public void setFree(double free) + { + this.free = free; + } + + /** + * 获取JVM已使用内存(M) + * 计算方式:(总内存 - 空闲内存) / (1024*1024),保留2位小数 + */ + public double getUsed() + { + return Arith.div(total - free, (1024 * 1024), 2); + } + + /** + * 获取JVM内存使用率(百分比) + * 计算方式:(已使用内存 / 总内存) * 100 + */ + public double getUsage() + { + return Arith.mul(Arith.div(total - free, total, 4), 100); + } + + /** + * 获取JDK名称 + * 从运行时管理Bean中获取虚拟机名称 + */ + public String getName() + { + return ManagementFactory.getRuntimeMXBean().getVmName(); + } + + // 获取JDK版本 + public String getVersion() + { + return version; + } + + // 设置JDK版本 + public void setVersion(String version) + { + this.version = version; + } + + // 获取JDK安装路径 + public String getHome() + { + return home; + } + + // 设置JDK安装路径 + public void setHome(String home) + { + this.home = home; + } + + /** + * 获取JDK启动时间 + * 格式化服务器启动时间为指定字符串格式 + */ + public String getStartTime() + { + return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate()); + } + + /** + * 获取JDK运行时间 + * 计算当前时间与服务器启动时间的差值并格式化 + */ + public String getRunTime() + { + return DateUtils.timeDistance(DateUtils.getNowDate(), DateUtils.getServerStartDate()); + } + + /** + * 获取JVM运行参数 + * 从运行时管理Bean中获取输入参数并转为字符串 + */ + public String getInputArgs() + { + return ManagementFactory.getRuntimeMXBean().getInputArguments().toString(); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Mem.java b/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Mem.java new file mode 100644 index 0000000..aecb3a9 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Mem.java @@ -0,0 +1,81 @@ +package com.huacai.framework.web.domain.server; + +import com.huacai.common.utils.Arith; + +/** + * 内存相关信息实体类 + * 用于封装内存的总大小、已使用、空闲及使用率等信息 + * + * @author huacai + */ +public class Mem +{ + /** + * 内存总量(字节,原始值) + */ + private double total; + + /** + * 已用内存(字节,原始值) + */ + private double used; + + /** + * 剩余内存(字节,原始值) + */ + private double free; + + /** + * 获取内存总量(GB,格式化后) + * 计算方式:原始字节数 / (1024*1024*1024),保留2位小数 + */ + public double getTotal() + { + return Arith.div(total, (1024 * 1024 * 1024), 2); + } + + // 设置内存总量(原始字节数) + public void setTotal(long total) + { + this.total = total; + } + + /** + * 获取已用内存(GB,格式化后) + * 计算方式:原始字节数 / (1024*1024*1024),保留2位小数 + */ + public double getUsed() + { + return Arith.div(used, (1024 * 1024 * 1024), 2); + } + + // 设置已用内存(原始字节数) + public void setUsed(long used) + { + this.used = used; + } + + /** + * 获取剩余内存(GB,格式化后) + * 计算方式:原始字节数 / (1024*1024*1024),保留2位小数 + */ + public double getFree() + { + return Arith.div(free, (1024 * 1024 * 1024), 2); + } + + // 设置剩余内存(原始字节数) + public void setFree(long free) + { + this.free = free; + } + + /** + * 获取内存使用率(百分比) + * 计算方式:(已用内存 / 总内存) * 100 + */ + public double getUsage() + { + return Arith.mul(Arith.div(used, total, 4), 100); + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Sys.java b/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Sys.java new file mode 100644 index 0000000..370f331 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/Sys.java @@ -0,0 +1,95 @@ +package com.huacai.framework.web.domain.server; + +/** + * 系统相关信息实体类 + * 用于封装服务器的基础信息(名称、IP、操作系统等) + * + * @author huacai + */ +public class Sys +{ + /** + * 服务器名称 + */ + private String computerName; + + /** + * 服务器IP地址 + */ + private String computerIp; + + /** + * 项目部署路径 + */ + private String userDir; + + /** + * 操作系统名称 + */ + private String osName; + + /** + * 系统架构(如x86_64) + */ + private String osArch; + + // 获取服务器名称 + public String getComputerName() + { + return computerName; + } + + // 设置服务器名称 + public void setComputerName(String computerName) + { + this.computerName = computerName; + } + + // 获取服务器IP地址 + public String getComputerIp() + { + return computerIp; + } + + // 设置服务器IP地址 + public void setComputerIp(String computerIp) + { + this.computerIp = computerIp; + } + + // 获取项目部署路径 + public String getUserDir() + { + return userDir; + } + + // 设置项目部署路径 + public void setUserDir(String userDir) + { + this.userDir = userDir; + } + + // 获取操作系统名称 + public String getOsName() + { + return osName; + } + + // 设置操作系统名称 + public void setOsName(String osName) + { + this.osName = osName; + } + + // 获取系统架构 + public String getOsArch() + { + return osArch; + } + + // 设置系统架构 + public void setOsArch(String osArch) + { + this.osArch = osArch; + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/SysFile.java b/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/SysFile.java new file mode 100644 index 0000000..0782f68 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/domain/server/SysFile.java @@ -0,0 +1,129 @@ +package com.huacai.framework.web.domain.server; + +/** + * 系统文件相关信息实体类 + * 用于封装磁盘分区的使用情况信息 + * + * @author huacai + */ +public class SysFile +{ + /** + * 盘符路径(如C:/、D:/) + */ + private String dirName; + + /** + * 盘符类型(如NTFS、ext4) + */ + private String sysTypeName; + + /** + * 文件类型描述 + */ + private String typeName; + + /** + * 总大小(格式化后,如100GB) + */ + private String total; + + /** + * 剩余大小(格式化后) + */ + private String free; + + /** + * 已使用大小(格式化后) + */ + private String used; + + /** + * 资源的使用率(百分比) + */ + private double usage; + + // 获取盘符路径 + public String getDirName() + { + return dirName; + } + + // 设置盘符路径 + public void setDirName(String dirName) + { + this.dirName = dirName; + } + + // 获取盘符类型 + public String getSysTypeName() + { + return sysTypeName; + } + + // 设置盘符类型 + public void setSysTypeName(String sysTypeName) + { + this.sysTypeName = sysTypeName; + } + + // 获取文件类型描述 + public String getTypeName() + { + return typeName; + } + + // 设置文件类型描述 + public void setTypeName(String typeName) + { + this.typeName = typeName; + } + + // 获取总大小 + public String getTotal() + { + return total; + } + + // 设置总大小 + public void setTotal(String total) + { + this.total = total; + } + + // 获取剩余大小 + public String getFree() + { + return free; + } + + // 设置剩余大小 + public void setFree(String free) + { + this.free = free; + } + + // 获取已使用大小 + public String getUsed() + { + return used; + } + + // 设置已使用大小 + public void setUsed(String used) + { + this.used = used; + } + + // 获取资源使用率 + public double getUsage() + { + return usage; + } + + // 设置资源使用率 + public void setUsage(double usage) + { + this.usage = usage; + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/exception/GlobalExceptionHandler.java b/huacai-framework/src/main/java/com/huacai/framework/web/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..f788180 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/exception/GlobalExceptionHandler.java @@ -0,0 +1,192 @@ +package com.huacai.framework.web.exception; + +import javax.servlet.http.HttpServletRequest; + +import com.huacai.common.exception.DemoEnvironmentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingPathVariableException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import com.huacai.common.constant.HttpStatus; +import com.huacai.common.core.domain.AjaxResult; +import com.huacai.common.exception.DemoModeException; +import com.huacai.common.exception.ServiceException; +import com.huacai.common.utils.StringUtils; + +/** + * 全局异常处理器 + * + * @author huacai + */ +// 标记为全局控制器增强,用于统一处理控制器层的异常 +@RestControllerAdvice +public class GlobalExceptionHandler +{ + // 日志记录器,用于记录异常信息 + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 演示环境异常 + */ + // 声明处理DemoEnvironmentException类型异常的方法 + @ExceptionHandler(DemoEnvironmentException.class) + public AjaxResult handleDemoEnvironmentException(DemoEnvironmentException e) { + // 记录异常信息到日志 + log.error(e.getMessage(), e); + // 返回包含错误信息的AjaxResult + return AjaxResult.error(e.getMessage()); + } + + /** + * 权限校验异常 + */ + // 声明处理AccessDeniedException类型异常的方法,同时接收HttpServletRequest参数 + @ExceptionHandler(AccessDeniedException.class) + public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) + { + // 获取请求的URI + String requestURI = request.getRequestURI(); + // 记录权限校验失败的日志,包含请求地址和异常信息 + log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); + // 返回403状态码和权限不足提示信息的AjaxResult + return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权"); + } + + /** + * 请求方式不支持 + */ + // 声明处理HttpRequestMethodNotSupportedException类型异常的方法 + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) + { + // 获取请求的URI + String requestURI = request.getRequestURI(); + // 记录不支持请求方式的日志,包含请求地址和不支持的方法 + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + // 返回包含异常信息的AjaxResult + return AjaxResult.error(e.getMessage()); + } + + /** + * 业务异常 + */ + // 声明处理ServiceException类型异常的方法 + @ExceptionHandler(ServiceException.class) + public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) + { + // 记录业务异常信息到日志 + log.error(e.getMessage(), e); + // 获取业务异常中的错误代码 + Integer code = e.getCode(); + // 根据错误代码是否存在,返回包含对应信息的AjaxResult + return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage()); + } + + /** + * 请求路径中缺少必需的路径变量 + */ + // 声明处理MissingPathVariableException类型异常的方法 + @ExceptionHandler(MissingPathVariableException.class) + public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) + { + // 获取请求的URI + String requestURI = request.getRequestURI(); + // 记录缺少路径变量的异常日志 + log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e); + // 返回包含具体缺少的路径变量信息的AjaxResult + return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName())); + } + + /** + * 请求参数类型不匹配 + */ + // 声明处理MethodArgumentTypeMismatchException类型异常的方法 + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) + { + // 获取请求的URI + String requestURI = request.getRequestURI(); + // 记录参数类型不匹配的异常日志 + log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e); + // 返回包含参数名、要求类型和实际值的错误信息的AjaxResult + return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue())); + } + + /** + * 拦截未知的运行时异常 + */ + // 声明处理RuntimeException类型异常的方法,作为运行时异常的兜底处理 + @ExceptionHandler(RuntimeException.class) + public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) + { + // 获取请求的URI + String requestURI = request.getRequestURI(); + // 记录未知运行时异常的日志 + log.error("请求地址'{}',发生未知异常.", requestURI, e); + // 返回包含异常信息的AjaxResult + return AjaxResult.error(e.getMessage()); + } + + /** + * 系统异常 + */ + // 声明处理Exception类型异常的方法,作为所有未被特定处理的异常的兜底 + @ExceptionHandler(Exception.class) + public AjaxResult handleException(Exception e, HttpServletRequest request) + { + // 获取请求的URI + String requestURI = request.getRequestURI(); + // 记录系统异常的日志 + log.error("请求地址'{}',发生系统异常.", requestURI, e); + // 返回包含异常信息的AjaxResult + return AjaxResult.error(e.getMessage()); + } + + /** + * 自定义验证异常 + */ + // 声明处理BindException类型异常的方法(表单绑定验证失败) + @ExceptionHandler(BindException.class) + public AjaxResult handleBindException(BindException e) + { + // 记录验证异常信息到日志 + log.error(e.getMessage(), e); + // 获取第一个验证错误的默认信息 + String message = e.getAllErrors().get(0).getDefaultMessage(); + // 返回包含验证错误信息的AjaxResult + return AjaxResult.error(message); + } + + /** + * 自定义验证异常 + */ + // 声明处理MethodArgumentNotValidException类型异常的方法(请求体参数验证失败) + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) + { + // 记录参数验证异常信息到日志 + log.error(e.getMessage(), e); + // 获取字段验证错误的默认信息 + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + // 返回包含验证错误信息的AjaxResult + return AjaxResult.error(message); + } + + /** + * 演示模式异常 + */ + // 声明处理DemoModeException类型异常的方法 + @ExceptionHandler(DemoModeException.class) + public AjaxResult handleDemoModeException(DemoModeException e) + { + // 返回演示模式不允许操作的提示信息 + return AjaxResult.error("演示模式,不允许操作"); + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/service/PermissionService.java b/huacai-framework/src/main/java/com/huacai/framework/web/service/PermissionService.java new file mode 100644 index 0000000..35cf932 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/service/PermissionService.java @@ -0,0 +1,182 @@ +package com.huacai.framework.web.service; + +import java.util.Set; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import com.huacai.common.constant.Constants; +import com.huacai.common.core.domain.entity.SysRole; +import com.huacai.common.core.domain.model.LoginUser; +import com.huacai.common.utils.SecurityUtils; +import com.huacai.common.utils.StringUtils; +import com.huacai.framework.security.context.PermissionContextHolder; + +/** + * huacai首创 自定义权限实现,ss取自SpringSecurity首字母 + * + * @author huacai +*/ +@Service("ss") +public class PermissionService +{ + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限(true-具备,false-不具备) + */ + public boolean hasPermi(String permission) + { + // 权限字符串为空时,直接返回false + if (StringUtils.isEmpty(permission)) + { + return false; + } + // 获取当前登录用户信息 + LoginUser loginUser = SecurityUtils.getLoginUser(); + // 若用户信息不存在或用户权限集合为空,返回false + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + // 将当前验证的权限存入权限上下文 + PermissionContextHolder.setContext(permission); + // 检查用户权限集合中是否包含该权限 + return hasPermissions(loginUser.getPermissions(), permission); + } + + /** + * 验证用户是否不具备某权限,与hasPermi逻辑相反 + * + * @param permission 权限字符串 + * @return 用户是否不具备某权限(true-不具备,false-具备) + */ + public boolean lacksPermi(String permission) + { + return !hasPermi(permission); + } + + /** + * 验证用户是否具有以下任意一个权限 + * + * @param permissions 以权限分隔符(PERMISSION_DELIMETER)分隔的权限列表字符串 + * @return 用户是否具有任意一个权限(true-是,false-否) + */ + public boolean hasAnyPermi(String permissions) + { + // 权限列表为空时,返回false + if (StringUtils.isEmpty(permissions)) + { + return false; + } + // 获取当前登录用户信息 + LoginUser loginUser = SecurityUtils.getLoginUser(); + // 若用户信息不存在或用户权限集合为空,返回false + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + // 将当前验证的权限列表存入权限上下文 + PermissionContextHolder.setContext(permissions); + // 获取用户的权限集合 + Set authorities = loginUser.getPermissions(); + // 拆分权限列表字符串,逐个检查是否拥有该权限 + for (String permission : permissions.split(Constants.PERMISSION_DELIMETER)) + { + if (permission != null && hasPermissions(authorities, permission)) + { + return true; + } + } + // 所有权限都不具备时返回false + return false; + } + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色字符串(角色标识) + * @return 用户是否具备该角色(true-是,false-否) + */ + public boolean hasRole(String role) + { + // 角色字符串为空时,返回false + if (StringUtils.isEmpty(role)) + { + return false; + } + // 获取当前登录用户信息 + LoginUser loginUser = SecurityUtils.getLoginUser(); + // 若用户信息不存在或用户角色集合为空,返回false + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) + { + return false; + } + // 遍历用户的角色列表,检查是否包含目标角色或超级管理员角色 + for (SysRole sysRole : loginUser.getUser().getRoles()) + { + String roleKey = sysRole.getRoleKey(); + if (Constants.SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) + { + return true; + } + } + // 不包含目标角色时返回false + return false; + } + + /** + * 验证用户是否不具备某角色,与hasRole逻辑相反 + * + * @param role 角色名称 + * @return 用户是否不具备该角色(true-是,false-否) + */ + public boolean lacksRole(String role) + { + return !hasRole(role); + } + + /** + * 验证用户是否具有以下任意一个角色 + * + * @param roles 以角色分隔符(ROLE_DELIMETER)分隔的角色列表字符串 + * @return 用户是否具有任意一个角色(true-是,false-否) + */ + public boolean hasAnyRoles(String roles) + { + // 角色列表为空时,返回false + if (StringUtils.isEmpty(roles)) + { + return false; + } + // 获取当前登录用户信息 + LoginUser loginUser = SecurityUtils.getLoginUser(); + // 若用户信息不存在或用户角色集合为空,返回false + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) + { + return false; + } + // 拆分角色列表字符串,逐个检查是否拥有该角色 + for (String role : roles.split(Constants.ROLE_DELIMETER)) + { + if (hasRole(role)) + { + return true; + } + } + // 所有角色都不具备时返回false + return false; + } + + /** + * 判断权限集合中是否包含目标权限 + * + * @param permissions 用户拥有的权限集合 + * @param permission 目标权限字符串 + * @return 是否包含目标权限(true-包含,false-不包含) + */ + private boolean hasPermissions(Set permissions, String permission) + { + // 若包含"全部权限"标识,或直接包含目标权限,则返回true + return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/service/SysLoginService.java b/huacai-framework/src/main/java/com/huacai/framework/web/service/SysLoginService.java new file mode 100644 index 0000000..b9fb5f2 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/service/SysLoginService.java @@ -0,0 +1,181 @@ +package com.huacai.framework.web.service; + +import javax.annotation.Resource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import com.huacai.common.constant.CacheConstants; +import com.huacai.common.constant.Constants; +import com.huacai.common.constant.UserConstants; +import com.huacai.common.core.domain.entity.SysUser; +import com.huacai.common.core.domain.model.LoginUser; +import com.huacai.common.core.redis.RedisCache; +import com.huacai.common.exception.ServiceException; +import com.huacai.common.exception.user.BlackListException; +import com.huacai.common.exception.user.CaptchaException; +import com.huacai.common.exception.user.CaptchaExpireException; +import com.huacai.common.exception.user.UserNotExistsException; +import com.huacai.common.exception.user.UserPasswordNotMatchException; +import com.huacai.common.utils.DateUtils; +import com.huacai.common.utils.MessageUtils; +import com.huacai.common.utils.StringUtils; +import com.huacai.common.utils.ip.IpUtils; +import com.huacai.framework.manager.AsyncManager; +import com.huacai.framework.manager.factory.AsyncFactory; +import com.huacai.framework.security.context.AuthenticationContextHolder; +import com.huacai.system.service.ISysConfigService; +import com.huacai.system.service.ISysUserService; + +/** + * 登录校验方法 + * + * @author huacai + */ +@Component +public class SysLoginService +{ + @Autowired + private TokenService tokenService; + + @Resource + private AuthenticationManager authenticationManager; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + /** + * 登录验证 + * + * @param username 用户名 + * @param password 密码 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public String login(String username, String password, String code, String uuid) + { + // 验证码校验 + validateCaptcha(username, code, uuid); + // 登录前置校验 + loginPreCheck(username, password); + // 用户验证 + Authentication authentication = null; + try + { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); + AuthenticationContextHolder.setContext(authenticationToken); + // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername + authentication = authenticationManager.authenticate(authenticationToken); + } + catch (Exception e) + { + if (e instanceof BadCredentialsException) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); + throw new ServiceException(e.getMessage()); + } + } + finally + { + AuthenticationContextHolder.clearContext(); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) authentication.getPrincipal(); + recordLoginInfo(loginUser.getUserId()); + // 生成token + return tokenService.createToken(loginUser); + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + String captcha = redisCache.getCacheObject(verifyKey); + redisCache.deleteObject(verifyKey); + if (captcha == null) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); + throw new CaptchaException(); + } + } + } + + /** + * 登录前置校验 + * @param username 用户名 + * @param password 用户密码 + */ + public void loginPreCheck(String username, String password) + { + // 用户名或密码为空 错误 + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null"))); + throw new UserNotExistsException(); + } + // 密码如果不在指定范围内 错误 + if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + // 用户名不在指定范围内 错误 + if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + // IP黑名单校验 + String blackStr = configService.selectConfigByKey("sys.login.blackIPList"); + if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked"))); + throw new BlackListException(); + } + } + + /** + * 记录登录信息 + * + * @param userId 用户ID + */ + public void recordLoginInfo(Long userId) + { + SysUser sysUser = new SysUser(); + sysUser.setUserId(userId); + sysUser.setLoginIp(IpUtils.getIpAddr()); + sysUser.setLoginDate(DateUtils.getNowDate()); + userService.updateUserProfile(sysUser); + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/service/SysPasswordService.java b/huacai-framework/src/main/java/com/huacai/framework/web/service/SysPasswordService.java new file mode 100644 index 0000000..04071eb --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/service/SysPasswordService.java @@ -0,0 +1,118 @@ +package com.huacai.framework.web.service; + +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import com.huacai.common.constant.CacheConstants; +import com.huacai.common.core.domain.entity.SysUser; +import com.huacai.common.core.redis.RedisCache; +import com.huacai.common.exception.user.UserPasswordNotMatchException; +import com.huacai.common.exception.user.UserPasswordRetryLimitExceedException; +import com.huacai.common.utils.SecurityUtils; +import com.huacai.framework.security.context.AuthenticationContextHolder; + +/** + * 登录密码服务类 + * 处理用户登录密码的验证、错误次数限制及缓存管理 + * + * @author huacai + */ +@Component +public class SysPasswordService +{ + // 注入Redis缓存工具,用于存储密码错误次数 + @Autowired + private RedisCache redisCache; + + // 从配置文件读取密码最大重试次数 + @Value(value = "${user.password.maxRetryCount}") + private int maxRetryCount; + + // 从配置文件读取密码错误锁定时间(分钟) + @Value(value = "${user.password.lockTime}") + private int lockTime; + + /** + * 生成登录账户密码错误次数的缓存键名 + * + * @param username 用户名 + * @return 缓存键key(格式:PWD_ERR_CNT_KEY + 用户名) + */ + private String getCacheKey(String username) + { + return CacheConstants.PWD_ERR_CNT_KEY + username; + } + + /** + * 验证用户密码 + * 检查密码错误次数,超过限制则锁定账户,密码不匹配则累加错误次数 + * + * @param user 系统用户实体(包含数据库中的密码信息) + */ + public void validate(SysUser user) + { + // 从安全上下文获取当前认证信息(包含用户名和提交的密码) + Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext(); + String username = usernamePasswordAuthenticationToken.getName(); + String password = usernamePasswordAuthenticationToken.getCredentials().toString(); + + // 从Redis获取该用户的密码错误次数 + Integer retryCount = redisCache.getCacheObject(getCacheKey(username)); + + // 若缓存中无记录,初始化错误次数为0 + if (retryCount == null) + { + retryCount = 0; + } + + // 若错误次数超过最大限制,抛出密码重试次数超限异常 + if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) + { + throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime); + } + + // 验证提交的密码与用户存储的密码是否匹配 + if (!matches(user, password)) + { + // 密码不匹配,错误次数加1,并更新Redis缓存(设置锁定时间) + retryCount = retryCount + 1; + redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES); + // 抛出密码不匹配异常 + throw new UserPasswordNotMatchException(); + } + else + { + // 密码匹配,清除该用户的登录错误记录缓存 + clearLoginRecordCache(username); + } + } + + /** + * 验证原始密码与加密密码是否匹配 + * + * @param user 系统用户实体(包含加密后的密码) + * @param rawPassword 原始密码(用户输入的明文密码) + * @return true-密码匹配;false-密码不匹配 + */ + public boolean matches(SysUser user, String rawPassword) + { + return SecurityUtils.matchesPassword(rawPassword, user.getPassword()); + } + + /** + * 清除用户的登录错误记录缓存 + * 通常在密码验证成功后调用,重置错误次数 + * + * @param loginName 登录用户名 + */ + public void clearLoginRecordCache(String loginName) + { + // 若缓存中存在该用户的错误记录,则删除 + if (redisCache.hasKey(getCacheKey(loginName))) + { + redisCache.deleteObject(getCacheKey(loginName)); + } + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/service/SysPermissionService.java b/huacai-framework/src/main/java/com/huacai/framework/web/service/SysPermissionService.java new file mode 100644 index 0000000..92ab8a2 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/service/SysPermissionService.java @@ -0,0 +1,97 @@ +package com.huacai.framework.web.service; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import com.huacai.common.core.domain.entity.SysRole; +import com.huacai.common.core.domain.entity.SysUser; +import com.huacai.system.service.ISysMenuService; +import com.huacai.system.service.ISysRoleService; + +/** + * 用户权限处理服务 + * 用于获取用户的角色权限和菜单权限,根据用户身份(管理员/普通用户)返回对应的权限集合 + * + * @author huacai + */ +@Component +public class SysPermissionService +{ + // 注入角色服务,用于查询角色相关的权限信息 + @Autowired + private ISysRoleService roleService; + + // 注入菜单服务,用于查询菜单相关的权限信息 + @Autowired + private ISysMenuService menuService; + + /** + * 获取用户的角色权限集合 + * + * @param user 用户信息对象 + * @return 角色权限标识集合(如"admin"、"user"等角色标识) + */ + public Set getRolePermission(SysUser user) + { + // 用于存储角色权限的集合(去重) + Set roles = new HashSet(); + // 若用户是管理员(通常判断用户ID是否为超级管理员ID) + if (user.isAdmin()) + { + // 管理员拥有"admin"角色权限 + roles.add("admin"); + } + else + { + // 普通用户:查询该用户ID对应的角色权限,并添加到集合中 + roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId())); + } + return roles; + } + + /** + * 获取用户的菜单权限集合 + * + * @param user 用户信息对象 + * @return 菜单权限标识集合(如"system:user:list"、"*:*:*"等权限字符串) + */ + public Set getMenuPermission(SysUser user) + { + // 用于存储菜单权限的集合(去重) + Set perms = new HashSet(); + // 若用户是管理员 + if (user.isAdmin()) + { + // 管理员拥有所有菜单权限(用"*:*:*"表示) + perms.add("*:*:*"); + } + else + { + // 获取用户关联的角色列表 + List roles = user.getRoles(); + // 若用户拥有角色 + if (!CollectionUtils.isEmpty(roles)) + { + // 遍历每个角色,查询该角色对应的菜单权限 + for (SysRole role : roles) + { + // 查询角色ID对应的菜单权限集合 + Set rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId()); + // 为角色对象设置权限集合(用于数据权限匹配) + role.setPermissions(rolePerms); + // 将角色的菜单权限添加到用户的总权限集合中 + perms.addAll(rolePerms); + } + } + else + { + // 若用户没有关联角色,直接查询该用户ID对应的菜单权限 + perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); + } + } + return perms; + } +} diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/service/SysRegisterService.java b/huacai-framework/src/main/java/com/huacai/framework/web/service/SysRegisterService.java new file mode 100644 index 0000000..91403ca --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/service/SysRegisterService.java @@ -0,0 +1,144 @@ +package com.huacai.framework.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.huacai.common.constant.CacheConstants; +import com.huacai.common.constant.Constants; +import com.huacai.common.constant.UserConstants; +import com.huacai.common.core.domain.entity.SysUser; +import com.huacai.common.core.domain.model.RegisterBody; +import com.huacai.common.core.redis.RedisCache; +import com.huacai.common.exception.user.CaptchaException; +import com.huacai.common.exception.user.CaptchaExpireException; +import com.huacai.common.utils.MessageUtils; +import com.huacai.common.utils.SecurityUtils; +import com.huacai.common.utils.StringUtils; +import com.huacai.framework.manager.AsyncManager; +import com.huacai.framework.manager.factory.AsyncFactory; +import com.huacai.system.service.ISysConfigService; +import com.huacai.system.service.ISysUserService; +/** + * 注册校验服务类 + * 处理用户注册流程中的参数校验、验证码验证及用户信息注册逻辑 + * + * @author huacai + */ +@Component +public class SysRegisterService +{ + // 注入用户服务,用于用户信息的校验和注册操作 + @Autowired + private ISysUserService userService; + + // 注入系统配置服务,用于获取验证码开关等配置 + @Autowired + private ISysConfigService configService; + + // 注入Redis缓存工具,用于验证码的获取和删除 + @Autowired + private RedisCache redisCache; + + /** + * 处理用户注册逻辑 + * 包括参数校验、验证码验证、用户信息保存等操作 + * + * @param registerBody 注册信息对象(包含用户名、密码、验证码等) + * @return 注册结果消息(空字符串表示注册成功,否则为错误信息) + */ + public String register(RegisterBody registerBody) + { + // 初始化消息变量、用户名和密码 + String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(); + // 创建系统用户对象并设置用户名 + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + + // 检查验证码开关是否开启 + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + // 验证验证码有效性 + validateCaptcha(username, registerBody.getCode(), registerBody.getUuid()); + } + + // 校验用户名是否为空 + if (StringUtils.isEmpty(username)) + { + msg = "用户名不能为空"; + } + // 校验密码是否为空 + else if (StringUtils.isEmpty(password)) + { + msg = "用户密码不能为空"; + } + // 校验用户名长度是否符合要求(2-20个字符) + else if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + msg = "账户长度必须在2到20个字符之间"; + } + // 校验密码长度是否符合要求(5-20个字符) + else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + msg = "密码长度必须在5到20个字符之间"; + } + // 校验用户名是否已存在 + else if (!userService.checkUserNameUnique(sysUser)) + { + msg = "保存用户'" + username + "'失败,注册账号已存在"; + } + // 所有校验通过,执行注册逻辑 + else + { + // 设置用户昵称(默认与用户名相同) + sysUser.setNickName(username); + // 加密密码(使用安全工具类进行加密) + sysUser.setPassword(SecurityUtils.encryptPassword(password)); + // 调用用户服务注册用户 + boolean regFlag = userService.registerUser(sysUser); + if (!regFlag) + { + // 注册失败 + msg = "注册失败,请联系系统管理人员"; + } + else + { + // 注册成功,异步记录注册日志 + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"))); + } + } + // 返回注册结果消息 + return msg; + } + + /** + * 校验验证码的有效性 + * + * @param username 用户名(用于日志记录等场景) + * @param code 用户输入的验证码 + * @param uuid 验证码对应的唯一标识(用于从Redis获取正确的验证码) + * @throws CaptchaExpireException 验证码过期异常 + * @throws CaptchaException 验证码错误异常 + */ + public void validateCaptcha(String username, String code, String uuid) + { + // 构建Redis中存储验证码的键(前缀 + uuid) + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + // 从Redis获取验证码 + String captcha = redisCache.getCacheObject(verifyKey); + // 无论验证成功与否,都删除Redis中的验证码(防止重复使用) + redisCache.deleteObject(verifyKey); + + // 验证码不存在(已过期或未生成) + if (captcha == null) + { + throw new CaptchaExpireException(); + } + // 验证码不匹配(忽略大小写) + if (!code.equalsIgnoreCase(captcha)) + { + throw new CaptchaException(); + } + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/service/TokenService.java b/huacai-framework/src/main/java/com/huacai/framework/web/service/TokenService.java new file mode 100644 index 0000000..5db5d5d --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/service/TokenService.java @@ -0,0 +1,280 @@ +package com.huacai.framework.web.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import com.huacai.common.constant.CacheConstants; +import com.huacai.common.constant.Constants; +import com.huacai.common.core.domain.model.LoginUser; +import com.huacai.common.core.redis.RedisCache; +import com.huacai.common.utils.ServletUtils; +import com.huacai.common.utils.StringUtils; +import com.huacai.common.utils.ip.AddressUtils; +import com.huacai.common.utils.ip.IpUtils; +import com.huacai.common.utils.uuid.IdUtils; +import eu.bitwalker.useragentutils.UserAgent; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +/** + * Token验证处理服务 + * 负责JWT令牌的创建、解析、验证,以及用户登录信息的缓存管理 + * + * @author huacai + */ +@Component +public class TokenService +{ + // 日志记录器 + private static final Logger log = LoggerFactory.getLogger(TokenService.class); + + // 从配置文件获取令牌在请求头中的自定义标识(如"Authorization") + @Value("${token.header}") + private String header; + + // 从配置文件获取令牌签名秘钥(用于JWT的生成和验证) + @Value("${token.secret}") + private String secret; + + // 从配置文件获取令牌有效期(分钟,默认30分钟) + @Value("${token.expireTime}") + private int expireTime; + + // 时间常量:1秒(毫秒) + protected static final long MILLIS_SECOND = 1000; + + // 时间常量:1分钟(毫秒) + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + // 时间常量:20分钟(毫秒),用于令牌自动刷新判断 + private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L; + + // 注入Redis缓存工具,用于存储登录用户信息 + @Autowired + private RedisCache redisCache; + + /** + * 获取当前登录用户信息 + * 从请求中提取令牌,解析后从Redis获取对应的用户信息 + * + * @param request HTTP请求对象 + * @return 登录用户信息(LoginUser),若令牌无效则返回null + */ + public LoginUser getLoginUser(HttpServletRequest request) + { + // 从请求中获取令牌 + String token = getToken(request); + if (StringUtils.isNotEmpty(token)) + { + try + { + // 解析令牌获取声明信息 + Claims claims = parseToken(token); + // 从声明中获取登录用户唯一标识(uuid) + String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); + // 生成Redis中存储用户信息的键 + String userKey = getTokenKey(uuid); + // 从Redis获取登录用户信息 + LoginUser user = redisCache.getCacheObject(userKey); + return user; + } + catch (Exception e) + { + log.error("获取用户信息异常'{}'", e.getMessage()); + } + } + return null; + } + + /** + * 设置用户身份信息到缓存 + * 通常在用户登录成功后调用,刷新令牌有效期 + * + * @param loginUser 登录用户信息 + */ + public void setLoginUser(LoginUser loginUser) + { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) + { + refreshToken(loginUser); + } + } + + /** + * 从缓存中删除用户身份信息 + * 用于用户退出登录或令牌失效时清除缓存 + * + * @param token 用户令牌 + */ + public void delLoginUser(String token) + { + if (StringUtils.isNotEmpty(token)) + { + String userKey = getTokenKey(token); + redisCache.deleteObject(userKey); + } + } + + /** + * 创建令牌 + * 生成JWT令牌并将用户信息存入Redis + * + * @param loginUser 登录用户信息 + * @return 生成的JWT令牌字符串 + */ + public String createToken(LoginUser loginUser) + { + // 生成UUID作为令牌唯一标识 + String token = IdUtils.fastUUID(); + // 设置令牌到登录用户信息中 + loginUser.setToken(token); + // 设置用户代理信息(浏览器、操作系统、IP等) + setUserAgent(loginUser); + // 刷新令牌有效期并缓存用户信息 + refreshToken(loginUser); + + // 构建JWT的声明信息 + Map claims = new HashMap<>(); + claims.put(Constants.LOGIN_USER_KEY, token); + // 生成并返回JWT令牌 + return createToken(claims); + } + + /** + * 验证令牌有效期 + * 当令牌剩余有效期不足20分钟时,自动刷新缓存延长有效期 + * + * @param loginUser 登录用户信息 + */ + public void verifyToken(LoginUser loginUser) + { + // 获取令牌过期时间 + long expireTime = loginUser.getExpireTime(); + // 获取当前时间 + long currentTime = System.currentTimeMillis(); + // 若剩余时间小于等于20分钟,则刷新令牌 + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) + { + refreshToken(loginUser); + } + } + + /** + * 刷新令牌有效期 + * 更新用户登录时间和过期时间,并重新缓存用户信息 + * + * @param loginUser 登录用户信息 + */ + public void refreshToken(LoginUser loginUser) + { + // 设置当前登录时间 + loginUser.setLoginTime(System.currentTimeMillis()); + // 计算过期时间(当前时间 + 配置的有效期) + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 生成Redis缓存键 + String userKey = getTokenKey(loginUser.getToken()); + // 将用户信息存入Redis,并设置过期时间 + redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); + } + + /** + * 设置用户代理信息 + * 解析请求头中的User-Agent,获取浏览器、操作系统、IP地址及地理位置 + * + * @param loginUser 登录用户信息 + */ + public void setUserAgent(LoginUser loginUser) + { + // 解析User-Agent字符串 + UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + // 获取客户端IP地址 + String ip = IpUtils.getIpAddr(); + // 设置IP地址 + loginUser.setIpaddr(ip); + // 设置登录地理位置(根据IP解析) + loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); + // 设置浏览器名称 + loginUser.setBrowser(userAgent.getBrowser().getName()); + // 设置操作系统名称 + loginUser.setOs(userAgent.getOperatingSystem().getName()); + } + + /** + * 根据数据声明生成JWT令牌 + * + * @param claims 数据声明(包含用户标识等信息) + * @return 生成的JWT令牌字符串 + */ + private String createToken(Map claims) + { + // 使用HS512算法签名,生成JWT令牌 + String token = Jwts.builder() + .setClaims(claims) + .signWith(SignatureAlgorithm.HS512, secret).compact(); + return token; + } + + /** + * 从JWT令牌中解析数据声明 + * + * @param token JWT令牌 + * @return 解析后的Claims对象 + */ + private Claims parseToken(String token) + { + // 使用秘钥解析令牌,获取声明信息 + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + } + + /** + * 从令牌中获取用户名 + * + * @param token JWT令牌 + * @return 用户名 + */ + public String getUsernameFromToken(String token) + { + Claims claims = parseToken(token); + return claims.getSubject(); + } + + /** + * 从请求中获取令牌 + * 提取请求头中指定标识的令牌,并去除前缀(如"Bearer ") + * + * @param request HTTP请求对象 + * @return 处理后的令牌字符串,若不存在则返回null + */ + private String getToken(HttpServletRequest request) + { + // 从请求头获取令牌 + String token = request.getHeader(header); + // 若令牌存在且以指定前缀开头,则去除前缀 + if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) + { + token = token.replace(Constants.TOKEN_PREFIX, ""); + } + return token; + } + + /** + * 生成Redis中存储用户信息的键 + * + * @param uuid 令牌唯一标识 + * @return 缓存键(格式:LOGIN_TOKEN_KEY + uuid) + */ + private String getTokenKey(String uuid) + { + return CacheConstants.LOGIN_TOKEN_KEY + uuid; + } +} \ No newline at end of file diff --git a/huacai-framework/src/main/java/com/huacai/framework/web/service/UserDetailsServiceImpl.java b/huacai-framework/src/main/java/com/huacai/framework/web/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..84ecea6 --- /dev/null +++ b/huacai-framework/src/main/java/com/huacai/framework/web/service/UserDetailsServiceImpl.java @@ -0,0 +1,94 @@ +package com.huacai.framework.web.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import com.huacai.common.core.domain.entity.SysUser; +import com.huacai.common.core.domain.model.LoginUser; +import com.huacai.common.enums.UserStatus; +import com.huacai.common.exception.ServiceException; +import com.huacai.common.utils.MessageUtils; +import com.huacai.common.utils.StringUtils; +import com.huacai.system.service.ISysUserService; + +/** + * 用户详情服务实现类 + * 实现Spring Security的UserDetailsService接口,用于加载用户信息进行身份验证 + * + * @author huacai + */ +@Service +public class UserDetailsServiceImpl implements UserDetailsService +{ + // 日志记录器 + private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); + + // 注入用户服务,用于查询用户信息 + @Autowired + private ISysUserService userService; + + // 注入密码服务,用于密码验证 + @Autowired + private SysPasswordService passwordService; + + // 注入权限服务,用于获取用户的菜单权限 + @Autowired + private SysPermissionService permissionService; + + /** + * 根据用户名加载用户详情 + * 实现Spring Security的接口方法,用于登录时加载用户信息并验证状态 + * + * @param username 用户名 + * @return 用户详情对象(UserDetails) + * @throws UsernameNotFoundException 用户名不存在时抛出 + */ + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException + { + // 根据用户名查询系统用户信息 + SysUser user = userService.selectUserByUserName(username); + + // 验证用户是否存在 + if (StringUtils.isNull(user)) + { + log.info("登录用户:{} 不存在.", username); + throw new ServiceException(MessageUtils.message("user.not.exists")); + } + // 验证用户是否已被删除 + else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + log.info("登录用户:{} 已被删除.", username); + throw new ServiceException(MessageUtils.message("user.password.delete")); + } + // 验证用户是否已被停用 + else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + log.info("登录用户:{} 已被停用.", username); + throw new ServiceException(MessageUtils.message("user.blocked")); + } + + // 验证用户密码(检查错误次数等) + passwordService.validate(user); + + // 创建并返回登录用户详情对象 + return createLoginUser(user); + } + + /** + * 创建登录用户详情对象 + * 将系统用户信息封装为Spring Security所需的LoginUser对象,并加载菜单权限 + * + * @param user 系统用户实体 + * @return 登录用户详情对象(LoginUser) + */ + public UserDetails createLoginUser(SysUser user) + { + // 构建LoginUser对象,包含用户ID、部门ID、用户信息及菜单权限集合 + return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); + } +} diff --git a/huacai-framework/target/classes/com/huacai/framework/aspectj/DataScopeAspect.class b/huacai-framework/target/classes/com/huacai/framework/aspectj/DataScopeAspect.class new file mode 100644 index 0000000000000000000000000000000000000000..c5a6523cb2d8993d60e1b671f79ca90d8f841140 GIT binary patch literal 5462 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 literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/aspectj/LogAspect.class b/huacai-framework/target/classes/com/huacai/framework/aspectj/LogAspect.class new file mode 100644 index 0000000000000000000000000000000000000000..a0f39eef6d4cf3a03c8960d62ce660f48cd036ed GIT binary patch literal 9860 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 literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/aspectj/RateLimiterAspect.class b/huacai-framework/target/classes/com/huacai/framework/aspectj/RateLimiterAspect.class new file mode 100644 index 0000000000000000000000000000000000000000..166c7ae5427a65668027137fb457b59d6e8d84c1 GIT binary patch literal 4930 zcmbVQ`+F4C8GcW)+1YG{TOeSlV7b_Y5ExWcnoTJrhy+83B!mVncCwib6LxpDv$I^P zR>7i3ji_i9YA;$V^dg8#E?kVgx3>K+8ZJNeALvv2o|(<=Y<6i=@;tjUd(L;h%llnV zKL7WnPXVmO-@_pCK5vjvW6|*o~peio;cSUV8MOD{Li(A^I z_LzDnBUPxzTm|z~)L_0qz0WMZCak2UCzF<=J0@R!W(fowGeJ9uO`BwLhl*Osp(1K! zl1`YKeo4ihSVYSQ45!sf^qEQH5#x}+?)vuWN{P5;O`_^LXRBovANH zkudJUmlZ5gu@uV$YFvVhW5%^sD;}q7XhoVDrAZt&94pywritG2dLJv#`;*+=!;*@; z@5WaY)TyY)3V}I!;cZFB7%*%BWxpQJ7~5k4^XuFE>SZa&VcDt1v2;)AiRdBzG2k@Z4vSPBjky>t}~1o;@}3{Lqc_Z-f!S23ZC+ zPEjcvRbKM9s@Q~w81QjxK%m;gJ|0^;sI^-Ibblm-hXu5or(T&jHcScEE*zst4r6eIj@ zo+#n+WG0c;-0~{PSQT~jDTu1*2M;azpC<&$+BzAm)8HM(0QM>{RSZf)mKO8&;@eDV zZK-T{SF+xNe6T>3%+&+`~AY-eDz&2vCTx#14XRpgUD0@m&#IergE z6+Exv1?iPpd5+mb3K?~#9=OwGH^depIO zMy)i{OK=&FzC!7#;(?p?Wo(3Ja)1f+POC^4^!a9=lx#ODURh5$Tc^dlxa<1`$SNL@ z%mS@eKjURqJNtA;Cedfu-Fjb~D=c+UJ>Em7$luw;fV0=6ch>lGC_lzb#-?vz@a7=O znUgl00(03_9++M$D>xQeA&XC;ZY&2`{T{y!j(%AF3+_^GZMJy!m}!%sY|PC_#H92r z)@SI+v=;Ni%0ED5v%}iqB7PiT`E;2ncie2oYv+Z_I`;o{7h_pJ?Z?zaP z&ZPov9_vX*PZ`bpQNfaV>oL+*GsuF@aAWuLg}E!1wU4q^mtc46zE2oye0*~4jq4RD zIXJRh6<5UkSjo}a&X4Z`4JD+Po{1?zvc}z9-Lk3FLT-ia`&NyOn zUh&9tOMXXWLsI7Ij4nry?%SrP+-O%YTyTH!j32Oh^{HSJF3UOOXY5gge@$D*0eH?4+Z`GvhAS750{ zFyS^SwnZQFLUK}7Il9eKo^23we576>=X^4Ct+_#B9cqrov-iMomzA-j#%5EN#rZ|W zZMEz}(uYOuG!KuKtAn16Q!jvNUKtI`VIkv{C6Tg{25&l8!s}ESudU@+D2GtKmC0); z1YUFBPx3c!g}L9aBgl8&Yx(^KDKtI>pA`)w5EtAdKj63A1{UE>eoJN~z*{)Qr@(3a zkmBUwPBNFmgAFS$LXoG-Ay3N-GhKdj1%8TmN=ckCRpM$&)NUcs1HmtG7GBRue0Pe(pW!`Eq8Nojh=J3MW0)hbo{t4= zOM!;QmBU!rR8daUI9>*F7xrFwI);H*pLvZ0i&F>EQv8O)Px#+#JO=s zyauB33kH}{vHK|?P36YTi`PsXbRK@#=t>P2Ej0%ZVIld4G!bE zNlLkROrBUZj2Az2m66G@)D5Q&x@J3b_EFTK3-_X%ZrMqwdw4I}<$}M((<%q=9v4&z z?;aQEGIHp|I5jEf`9}N(zooemEW_{cd&1j78~=bm`r!3e{*;9`1o1D_DH37)nQLA> rSRw)8UuAV@6fb>@7MWwhjV7-q+(p0sg==N_+{IPq`d|6`Z@_;54r__) literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/config/ApplicationConfig.class b/huacai-framework/target/classes/com/huacai/framework/config/ApplicationConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..9181fba19e58419e9e41de991951a01a6c680e61 GIT binary patch literal 1874 zcmb_dTXPge6#hDwU9yZO#t;mkLJYXOknI41mqj62v5;DXBB^M>Q}1kN)5%QtR8LPx z_*M9#77zXa%im;qdS-#uOo5@?s_pAJ=R5tK%lz@z^WOn{hFdj^VZ4fo0F$UN%d3MCEpR@ZfZ2YTg+DqoO>ckq+QEY)R(sN!mw@E5a69E-VHE~ z8HU;1zs#wKZ>U&4GMd0^1L?k%TOy0d-Ffolun^gq#rsvv1vr6uhEAEY+s-AN9Nm-F zNy~TM;3dN`_tJ;Q^)l3QQjUnzuHplR< zJyDw~RfM>zwQ>0s0WOzj4ZX_AeM+2{qI^vXO>m7!#J#>)dK2o>3d5axsqv$--`He0 zRf0FnRfgOD3k;?4y2)%Pzf(R+^M^v(@>$Jry%ep4u_6&Nn^R5H-TbeLx3y1<)ij~< zzq?Ac`@ow01F~>j?k6Ud`F5Nd0b^+9M&pAXp`^p=9}Qbk*6U#&yvaQgW%RZ=O0rgw z%B^Oz73ZZ-y=RS@Me3|b?n$?8`ss89D>Yokl`5_V)U2*CEWYYbO7EFn$-8+%kcQY2 zp)>Y?{ESC;e-0}%Z)!uO0^p9&{Ya)uU1N5$q+JwnF1GG`8yh|OhvLRTBH!+^fPB5~ zuz!vE+YwQ;uAFSoV3An)qK1?BGQc@3G6dgiO)WDLX)4nyZZMobL{iy%tDv6#(d$L2 z`xoR8)JghM_}@5~|5nhrKs!z|qj|FVE7;Gp7{e(V{WUO$(=-N!0M6h;+7TxFN5?C# z(9X+Mo}&spqt&SPnGx;EQ2S#qO*&_RnVNq;t-YnIr0Fe9HGjkLU+~_K|8`~6*c3j& z5(!V>JdItv|$2u)n1#ihq!DVEWq3;zyILf7#*{lB7<8lAN9HEv?z EZ}WjE+W-In literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/config/CaptchaConfig.class b/huacai-framework/target/classes/com/huacai/framework/config/CaptchaConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..2dbdef0a7155b92e54020b431f83a42ba2fea750 GIT binary patch literal 2438 zcmb7FTUQfT6#fp^Ax5NwqNqhs)P#f?ko!d|grb!YP$)|6#mVH59GJ|+nMo;KeeJ7{ z^`*<-(7u$l%3A#a{V)9sUEL=qp#g=`td*I4&g`=<-}lX)KmY#y4*;VGThYMKXM066 z?|FHLo3@wZ=8jbg?Sf^dJU0j}Hw+?ZLUSuxqG*N2(6McOYnhJa=FOGtHn&5DmRaG7 z@Bu?(EWXi-2t(qesG?YM%muz>m7Q=&Ws0Cp#y`(+u@br*3ddaay%P6B!6|(d%^Erw z+N-4*Se)Sp$yoZ|YS%(vxcQm)cj9$hs-+nXR~T;8 z@`e1nu;hEWvdw*C%X34+_8gB2xXRF+bu4>F!*zzvn!hMod2Z~ATv*W1Lq!gc4{7M7 z29J6axX2em4L2DYq(?tPPtD`~oPqeBYq&$cBa<3pb@>7w1j2KT9lobwfIz4QNfDnK zk_^2cj7lu~hQr;wEYo0MqZ)=zZQHeqJkW5D;ryI0EGL=r%D#|Pj51uSCGoO>P1{?- zwnEQ0B;^{$8Db;~LrIMQ%yFZ3KzbN!1uN(6J|cUs6_X6NPLfbXAEeGLk&j>k(^8i+ z4Bb_(kI82yz99v6KZ*zVgrSY}q0Ul0vGf^oPyh{cb=)cS)g+K&hzt*nC5I=bl2gRt zrwmP5r%bp%l1zAE~9U;q>gnNk!uC;%JYK7J^10^ULg|cN^!r1Z&_+8K6p-tJh zv7}y|brNRE=MrWOD<@G`;W;Q-HWl@hXdcn9Mz?VdpVx`V^+dpHT|FmZ?-oQzCpH=M zTBXWImH3jO|5QRVUPgv&#a9e*3AAd)JZy&MjcmqDsb*@lG)dVKW@+K_Ou3ll{<@WQ zXh%bMS`|hNw@(aT_ddwi;91!XMUiiaKoClEuIq(XNYjBp8cchB z-V91|4jy4aD$ukJT}=rJ#cGo8J27D^f@rQ*Mj$b96hA3xQqnOuwHsNZ5n=O1QDN{1 z)rOqS4C805N(rRwSK66DAHJk7ll(P;$!`-~zowN*cSiT-fqgXoN=5^|p=(QJvjeZl zjw%OO$kK|zMvlDX;lC)D4Ep*2ZJYZzmp(w0?(NG-{eu3{Z|HoU)GzDZ%DAR~tc)9a zpE7Rgx0P{Mk1Hdg4=Tgdhm|p+k4fVI6Pw9>Ol1<}Fq`;=lBzZc1GxPoq6 z#Z6pC0zLHA?!|ptEutSQxPupnQ4?`&V*nm`yrPo>s)1^A8U#4DsL2l6$)iA7wh1jk z*GBA+e>**M=wEyZ7QiLTtHHsHDje@lf#ab*uNu3oFDTMGI&Ez-(nR%<7pk9?+)?&=@8F1RXpFGf@g{FeoZwsz^|Y!Wf%be E0L+Gl`2YX_ literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/config/DruidConfig$1.class b/huacai-framework/target/classes/com/huacai/framework/config/DruidConfig$1.class new file mode 100644 index 0000000000000000000000000000000000000000..4e0024aac40da5279eb01845ef468268fee55415 GIT binary patch literal 2112 zcmb7F?OGE@6n=+9HiT`BC@57?k!nIPBdr!|XsjT#v?!D)_T$$inIwywopp9X=r!~z zdI|ke`|+a}(2MHRGrK902+u>4XJ_Z*%zNH_=bSTt|MTZx0Pf-YF^nMPAnoD|Mi~lq z)#2@~s0)ubwdly6(yw@31x>HTpXjdFSdII0iwvWt?L~8UY18H@ca^iqV9Y_*#W-As z+@9DM+!sNMZ`Jl>-4N)q7kH+^kjfW!$MHVSI>@;=hY5yhYkt5ZsrP+p_`2sCsaM;= z3m7sDm5doa&uN`3QBuX7MF3w!BrPCxW;hBDkyxfCTfB= ztj@e^JfFXyCL*D<6peMQIy-$ZhFi&3ChAp<_JUTq5P#ahR0cN~rj|u%{!vW?fz-2= zWiBeqHO(s-++;u&AL5pS+b;4b44g@V%8=DE^hI4h_I-v2$M}8M?XgUX43|RHlUg>2 zPSn=&RjEgZZwXn=@3<&|GmN#Qd8s`*T!xGJK9r~UHhpA&fg))UA36Bgr69h~a6XQt zK^ulMJ&VFHc?{>mu7f3p3xkLIUkokePaV^RU550kYLK6^n_eKFbvrewpNpDLGZUMt zF8p1gJv;7CrfuRd6i@47Zm~=*Y+f*28xHP%7Q|UevCH5#le~y$D6)paeZZd1cI}`p zL*r5CJ9xq{GpI*Dbqr^JdgmP9$pwx7}z z3H78*TpqIQs}cJrjg34YjVEHGfi!ZkbtK3(wvGtmG^nU(^&@FnjT4

Ev@qs%6QF zO5w2<)O{6EraqOXtr`wqFx)<6?R4=H-!k0LvZMB844#nRku8r12z!PrQcx}YDGA=e zauS8MMDUtYhKsIINlou+$wRF|sf|Y!Du%5ZVYUzFsHiC2;>q^6S3-=_leM90JWPfH z*WIvXXVn)`BqLm>`z8a1Pr!0Hd*{$Kl%{7K4`}vN>Q%^Mgx=Zt;;&%yzrp#1Mk9Dg z?I_s*0%dBa5-Tj@GaA`CX3-e^D|kd}7VADe7K;r-$RiN!5Q6RwJSKn@+LEUCDm~}0 z)(86;DRKcU-6<~of%j6N_B=()rQh+v!}N4|;`+qw8_dPs`F@v77U<$izro$N6lUNM z%pU3B(*+k`5+UjG8bxT*C5oxtOI#gNTf#cNAUfn8Ut%NvswI4duZh$oX0eHrFR<7v5W7hmn91;CjI^ejh8k? literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/config/DruidConfig.class b/huacai-framework/target/classes/com/huacai/framework/config/DruidConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..6989fedb6aafd51aaed5b3c0cb9fea7df0abbf30 GIT binary patch literal 4776 zcmb_gTX)pf755n;h)rm1FyZ1`ZJ5+xNHjCDEu)bplE&Bx z=`Cr~Hoc^mr1x9;(ATC9CM~O7edHn%rG<9ADFGzEg7SMRTd<8rlW<{etf6b56}I8Dllm zDjO~Zlw3pi4S|b2y%V4Oz!8b@5fxq7BakUdDFS_oM^_!kYK6t^IqXyLsEYmQ7I;=7 zTCV9O-P9Icy<*&T+&fyRl~#3~s^R*k;f2S{)W-tZa$Qw{*`5T=62&K(AaS9B#{~8U z{;K(=rCrj!@%z z+2Y!Ot>**s60T?XjbliP2xwqwxON9-1UjPh+t&exZ`%T&*96oRkEu!q{LqS*MFB4=__B(5+-OSm zp<|h{T%&5~C1cdGT7q$VQpd;;E$YC6z+taetvar+E&F~|bB&POo)!nB;I=@oG}Lza zH5Pw-Aa9_CY??l#uL{Eq}kjQgB=!&T1cCQ;yfp_s#`UB@X0XS z3^kJdP++B@&?5p*Y_}jNWCEAA2h`ka5nkDpT5CiVzUkJq9idt$ruM39EShUf_J%F_ z#3pIivcAe%T?oOZeOF?>TBmn&!P$m!4b`^RF-q+YIG;e5y}s@;t2IbTcXfpUQZH(9 zds7IoXNkh#RP)lavC!JKk2ps>d&{lGR^WJ|kvAP4W66wbR&-YyGaIJ2si6PK#e_#G zk@>kuN`>Edvvid1uxp60cg~qhw(he<7FdYt?WWETsmNA|TW4f$Ipi7@haHjZ#FJB< zz}rc!>c=gU%nwN+_mFv4TDB2j0uu>M^v6afFCI~Qx9};R+gh!ugHjC(H5nZi<11cN zy2*=0(=s?>QEBHZ^KvXmnhOdux7$*f1fKtd_Kiy7)|BKm_fJFhERhYbZ=?_Tx+hkl zhh&$>-Z&9aLijK<1vkSWJJU?+W!pB~^Oo*;hK&A}4hx6ay|?k!3bcbcHTYZMNaING znd!R^@d01jU`LC}#g(CqM9#_fD)AIhy=k5=oY+(0f|G)h1`hWB6-QEd zv5&((4s(MW&>rB_eE-0GoR*eJ1?J6hJ8*_~?dQ;qVI1atqhX=Jx`hU5>^CU5gBQRz z@l6W<7WYYqgfnkNHp>Mz6A|8T6P|Pw?`aX-_bE5Sx%}XV82%@|IGpYu$frZBe?n$B z{Q;K)`v_)?&?E!8gAJF_i7T9+;&=j&(c=>4Nq*}@miBlfP$XXmoDX2iv-0>Z-lll@ z_C3C(>d@uPn|O;7zt67<+Sp@`3H*SAHvEv&@8Cxf?*C9w2j{2zH*jH+?7H$VWaWdP zbJJ6Uxi97BHgJ77ox3@ld4OB4T@QyhPO!3+c<)k$+&f##;d&ky+*z71zOLBQ0lr)g9kc$OsO p>wJb3{7p!~6n@L;HvBGFCL{Ux9RGp8k^xtFD}0{{-Qt)({|Bm6b5j5S literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/config/FastJson2JsonRedisSerializer.class b/huacai-framework/target/classes/com/huacai/framework/config/FastJson2JsonRedisSerializer.class new file mode 100644 index 0000000000000000000000000000000000000000..d336f1ade1a4b9b64a3bfbce5217a5bca47c2492 GIT binary patch literal 2763 zcmbVOT~`}b6x}xoOcI8VhC%_QrIkV;l&M%NB}n;bQosN$89+phH^~qN!VGINsO7;| zpZp2_2c&9QYWt{b`J-H(J2Qkq0Li zGqh~;uenm@MoC%9Z)*jcp?+L9bbEp!7#~=TAdHBFsEh{445I~eSJ~R<1+FWb7T?vr zF|8e?U>cixNlEjHJzp`65%E~oih3ohSvoK42b#rDS19v?1BPpvQ^l zGX0z9d9G{{(zZZS=CezSYnE(1;cp*AQc9a+VyFMIys6n7JxUvH{o8 zCZS!%4RkPcUA&P&woO4KYg@WeVt5|+`QXZy+_<{s0pD&HGAU&4lJO4SWoUkqN>8n1 z)ZEPM)N(eZ5;u2)p_@u!i(3`VR%YBW8O2T9lF%dLHtw7W=Ly+xNwcS4*jk0*rpLP$ z(7?vD!0HuwahKs%d}I1zt8QNO%ZOuuj83hnOF4D@St^&F%c!YkhPxN2v8k6SluFte zDDa6(1HY{C;%VST!0yQyhQiRWCuBIu*zf)E@D%lF>}J`O7+n^{vuu+7D0(>%#`~1# zD{A`QXcQmdfrJlbJj6!~9Zt^I(49frG%YuIhikauS*fv;a^?}nBqU{w<70+iUun|o zu4#z6tk~S3Z1G+w2Vx?Tb8EA6YAQ3ARdZRD!qvLrlgkq@ipQ9gFePIeGYmZ!*M}<% zRU_ZGP4&f|=7yJHAzlM_;DSI1UY^8L9?rBz+Gw-fD3&#gvL(gv$h1mIWlz-M>G@OS zHdich;3$>qiKCo5fg)MGWH@ICL%YvmCqKqjH93)Fxb6>vn>hok451mbNass)MmMy@ z{oTA~sXSk%nOMdw@bW6BgO0wv$&kIJlh$L7t8a5$5|Q?^z$B{&{`jp?Pkih==cBtK z9F0^JBntljyd0-Zu18HQ##nW~YzTbGuq+e*nx zubE2NVR-m|rQKVzAQ{?f&Qm7}(y7shc z`5C=E$1-6GyWbH9VYfT+7o^}H;lo7eH5%80M`+F-qxA@#zoPq)ehB%f+@Y^ta<$J# zrMpU{8(FAiXz*u9N9YT_#^5oAPI<;?BQQjA4U;hU>7p~@aP4v7s$Bc=1->Mc zH_(C{z9K0EYY`?g>aiAFLkL9JIkKEI$fKVA7E%s7yMS2i{xL>gqVou&$C!ADJ`qah r=qK)=Kr8KQBPMPt6i}qO05ojU*hTY=C_$g>#2_^yF>cdXu8;f+vAy3t literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/config/FilterConfig.class b/huacai-framework/target/classes/com/huacai/framework/config/FilterConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..6b9cb4be5cd31b191d84670a6ebaf70e08f3e2c0 GIT binary patch literal 2391 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 R)e{%WBmDNWBr{4o{}0}eTCV^A literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/config/RedisConfig.class b/huacai-framework/target/classes/com/huacai/framework/config/RedisConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..56c8fe40bf49ee9c29eaa1534cb4d4618ea4f735 GIT binary patch literal 3104 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 J>(^g_{{iJC@1Fnw literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/config/SecurityConfig.class b/huacai-framework/target/classes/com/huacai/framework/config/SecurityConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..77466bb5e4445ba9d90a807dcbc6ddcd5988fe85 GIT binary patch literal 9255 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 KUD{RddEhU6>TP}i literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/config/ThreadPoolConfig$1.class b/huacai-framework/target/classes/com/huacai/framework/config/ThreadPoolConfig$1.class new file mode 100644 index 0000000000000000000000000000000000000000..ff0432217fc409ff8ba74423d7563bb1ec9fda57 GIT binary patch literal 1304 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 UF7~*$ChXVNcvj0aiN$l^AKM$vrvLx| literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/config/properties/PermitAllUrlProperties.class b/huacai-framework/target/classes/com/huacai/framework/config/properties/PermitAllUrlProperties.class new file mode 100644 index 0000000000000000000000000000000000000000..acb2087cd3b09595e6680dc683c61fb9e6b1f395 GIT binary patch literal 5919 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 A0{{R3 literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/datasource/DynamicDataSourceContextHolder.class b/huacai-framework/target/classes/com/huacai/framework/datasource/DynamicDataSourceContextHolder.class new file mode 100644 index 0000000000000000000000000000000000000000..9c839a52993e553b3529a4d0dc64c4f7ea5a22e0 GIT binary patch literal 1292 zcmbtUT~E_s6n zrIfkLt9H#%HDzww;uS-kBg=|sDcF{)y>%CDv#dG9Y1%qNtY|yBQZ?6SHkG2S>zb1d zqlaPOWamSD_w&il-n;kpPkZ&&yBzU63(|iwr%5<)za6^U}-3<>K7@Gls!{M`^>+c)4h++{{KXj1dW=GA`jV zL!ucefTH@U8cCQ4W^x)=P+XO94P%7U1#Tdf4uop}C0sw#7rzqWd_%^BXj-SERqQP- ziZn73CS^?FCS~3#%mPSSTKYF z$$#(2w3sX!k}xeQ=o%86M^D3IV8Nhx)?2i`Osp`Sx5=W=ozcA`a|HI)gWRE0daXiE$ zkMc3H4azZ(5Qq}bA+G#Fa*q%U&7_i)LhdyGWCJgG_;C@|2aEp|FiF5ElH|#$gW}nN gcr5%4sRKwqF#O^O(+5bjI(L&}oapjI)P|>j0VY&5=l}o! literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/interceptor/DemoEnvironmentInterceptor.class b/huacai-framework/target/classes/com/huacai/framework/interceptor/DemoEnvironmentInterceptor.class new file mode 100644 index 0000000000000000000000000000000000000000..80c445d8e92350486b3860c3ec3f309da62919e8 GIT binary patch literal 1932 zcma)6-E$LF6#v~Mn@!S`()5FdqJWk*DYc8WR&5Ge)09?Hz@+Wi%8YZnxlI<5-MG6c zFpdw7`Ubw>i|C6CGrpjX9|MSdy!&qmrEk9a;E3lYp-l@~XOg@3o_p>&zu)=#?eAN6 z0Ssd<1P{C_d|@c?GweLgSGZQ>W?nn7bXw>xgMZ904fhCxHx{1{B7mTZP#8@JGbD7Y zq!lV$=Z3ay^O87g*=IDvbcL;pvTNDegeX}lbH%VNvm{J+dgF?r)o`|4GqA-b>_Cf( zonf>h!f-$$I;T0pUMUJ!E4Xf1o1!IK6J*5u72y!f=Dcw0hq=r&LwhWq-XN28ZNtnb zB>eU;I?%}wPQ8;%&rPJ>o6DrL3~lc|sUAWXqAGTW(TzQuf>X1{5YR2t<%a1nbjKPD zBn#ZmN@%7o67edhXT#Vl2?Yu0jBpE9E`a?M)uX9d>F4<{dT@Z8&?{bart_v{izIgh zLr0_KI+OUKiav(nh6K>IWSN>cCqr-0ZcA`ek1qm_UIKK*FzyPIqGzFhnI!s@qIS1(<>f9Iciqi1MQ8yzJ@)K3(E@p<iaJ>r%b^THND0br}|_FQP~tLIWcD!CATL!hq7uvZneH` zT~&LMhy-j=^UAP2jXFN^RH)^DGmzRmBBP-?kZPOzOVw7QI?K9TKQqJ2)l{fRGxTmZ zI%*zOISoSV-QlP>N#kH60_!)#XG|~Fr(q~-RcOdf8nPJnZfUiK${m!382X=Dq7Ds8 zVY%naLQ7gD$`6gm1oqRE@_^wbT0L^m(*9){*G=>tq5mrmUd3)X#YpHS0gY~2U!#*o zUq)Z0{|49(wDDk+R(TDyAVI6NMhY0iVfr#0!Rw?&k69NP3zF+!ZnCVB6Jdmut>3x z*r!N)fEc`iaf%Tkt+SB3H<7F|JB10Ph>?^{;wX7*#W8w04j-L6F-%p_>FaPTD@QeuBKd-4JXa$$?$|JLp>QMqbD+_##?% gL5aMW{RwR{{1gXTYvJ!CP#HRTkgcXuN@sz800N~5%K!iX literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/interceptor/RepeatSubmitInterceptor.class b/huacai-framework/target/classes/com/huacai/framework/interceptor/RepeatSubmitInterceptor.class new file mode 100644 index 0000000000000000000000000000000000000000..ba9ed832a54059d05cadbf60eb2e23c21cac5cda GIT binary patch literal 2087 zcmb7FOH&(15dKD5X|Pyqj7ef)?AQ)Al6Xu)$V+RJ5NwQzfRq$-@NFaw(qd^>*&W&B zr}#hQk_#a@q?}6*`9;aam0s>{RzvO*5n)*p97N7?QdCF2@MNyz4cD-|=jxvFFLQdgXdA#4A-5ZKa!T zU2I6F-csIHZ6O0hD!~l!&8RoT)xyd=Bd3FSrg%{#c`P-Uvt}{*_AbE3tF~a zM@YwUwbV8(%c`TKu5_zyTW2tis~i(1u3?hl`dNAz#t6aci4un8TnxloOZvVQhZVEe zm$EW=A0KdBH<85@!TX-wj#%B72W91VTf~!;@;ujLSc!);(wZLvceoyF6!$U9ku#A;fnh2D zAX|1-R;AdJzTWp;XGv^UcD5Nz?FNg`2*drj<&eGu**tM^9t#|cCImiam^tlYSKBRL zL=k@$Y{-_UoVxOkZ9Tj=QGUmDd}YN5b{q{Vqa}u=v&!f=_fZ`UFveJO>y*1ICEHQk z-F8)ZKgeo}P9{ojO}2KWX9xS}(9lhruovUueL z{A7T8VFfeH_AHziq%f8YBc2N5MFn}GP!w6R6vZOoM!zFQVWv=3w5+|@lAVYvna2k~ zyQ1Feb9~2etuJ$OL!MXMF159FJK)@Oym`fFx)_KuWXoMg+ikUL`!;d1-$99zg`tV@ zsb?xz|I|@p&23ZtC`Vhy9qP9UFnmH!R+46Vsc8I^c7kR`bE@zw*e|q5(3^(dH86?K zXl;f+;B$OIJL=Z>iv9|O|0QK3G;c2a3H}<77T$mtjjVBqOT|<+H8FOGD{nAXOlQ-7 zVWu#bO&{XD*Ot7S z!aW)V?!uz>`Uo>v$1FB5*F#z+G#1GfY2+kj;Q=1PAlYTC;A?XGHgfm|tK=V{jywuh z-(s!jv%#~HfVhniMLqi;2h(m_aFN8DIFU$ZyJ;(OwlfZQg7VCHqAkN HcYyx@2YOmr literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/interceptor/impl/SameUrlDataInterceptor.class b/huacai-framework/target/classes/com/huacai/framework/interceptor/impl/SameUrlDataInterceptor.class new file mode 100644 index 0000000000000000000000000000000000000000..5fc9c11f5aacfd6c1ccd71ad07484dae7872ecbe GIT binary patch literal 4561 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 wCWj<|nT_C;1lPP>T<9nlYZC z%2~4M3_4NpO*qK7P;}J>@Jo%XA-@Oy1>kn-3jhEB literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/manager/AsyncManager.class b/huacai-framework/target/classes/com/huacai/framework/manager/AsyncManager.class new file mode 100644 index 0000000000000000000000000000000000000000..0943da91662d33fa3d5b9ec02ec2e614a1c9e461 GIT binary patch literal 1443 zcmb7E`%lwQ6#niu>=;F0JQa{2ifjn=0r)7Q6Gk#Jz_5WKF(H@k$_iU+x)$`mQj~z2 z7=QMUGM?KNs04g0*}eCio_^>1&N;U~et!7|U;>#0B8V!8sc66@hSqJq%e4x(wzTEV zZDF_!m!?h2bY~c%BdN7U#L=iAp`r;YL&~tLTG`_UH?`LeuZlg}+0m-p;# zu~;`4lFKi$E16<;V=kM|tZx)^i&+Mii{mmwPt7Qcl2;L>>|0@YuI&_rvuhe6fmXCB zNUCT@2SYlPkB(K_(mdC!)U?_g$0W`Le_NF<24zdQ&jq&_hDY+Jx(cog)l(r?U8=62 zi($-G=`^Krc1gieg8 zNFhy9kNd^&DqPzY&$9StMd%BE0Sl^sVS(m7*K>rPz-^2v7+0a;4p9sh<+t`1%Z}hB z>UnRi?71a-&&pV(%pN!0qHwCF#a+{;IYr&E=N6#w(tP z$|5twX6+KSs3mV&V#%v+3a2PF(}d@3gICtLV@f-4#@w<=#KuNa|Itz!-uf^RYEPK7 z>;|Q)?GQ&|;?jwsGgR!;0NJIW7<8OAD#0~Q6l~8i#Jnjfw}tQMxEvS^lJp2EjUp*8 zIT-Gd6~TS_)11p1$U&vOfjsoppoj^w7Rh3?cc%}*(yfUDDCuPL5n8^Z^8no+=urfd zv?dXwwV&2@g6csRou|knQ_<^q;Da}k_hA6t=RW~r`{;d7G0ex2iGd+nAK@{rlEi{f zigKhsp?kCg1N#{Kj9U$Od758-@_;e4VcgH#<=e?1ujHp=n(!E&oQRw9Z6Gdjk1%=` qZ4wG$ovMfjXmQM(U<~NAyaVu$Td|o{}$uXTJdS?Oaa) literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/manager/ShutdownManager.class b/huacai-framework/target/classes/com/huacai/framework/manager/ShutdownManager.class new file mode 100644 index 0000000000000000000000000000000000000000..1d1497c54e571908a7f4687893d9472a42781b66 GIT binary patch literal 1337 zcma)6OH&g;5dJ1SHiV6Y5I|7GL`1@CRqz==5#Gy1)Lqdw=}d`vzbf_X6;tPC>nj1~f8+ zR`sX4meH-W_GodH8!kiRglU=X6hqxWd?5foniK?7G|TJ5hRA9wdEL-WZQ0hdd|lXU zT2{C8G`F?XO5R-(>(*Rp&k%9SmT9M88D(=5xeTf}g%*mH5otm)gp)jR8VHiDG=b9!=Fepph zacw~)yPo7N*Ua(-(=o~Kv}FlbmkN{H9#4-AO+PYfk8OUx1SKzwXth^qc2pfY3#MG% zSew&xWv2cKBU2utKuY9ogU^~Wtw;^IQCU2yPDz`bY{kk{IWD)ka0@xE&4_GHSln_G z)Cqo?V1S`pju5TtX-%_C?=jkG^kwvI82k#hNedrMJ-lNO;2N#f(gj?{4cal>q*I>< z9;*T?6|nqg0PPg1;}{3G%INhk%r ol!9oGgqk`c6ZNhEP2r&(jBG)b*|92VLgeud*?hQ*!;GeZzaD@@;Q#;t literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/manager/factory/AsyncFactory$1.class b/huacai-framework/target/classes/com/huacai/framework/manager/factory/AsyncFactory$1.class new file mode 100644 index 0000000000000000000000000000000000000000..68231738900acfd27f1e339467699cf7ea572e9d GIT binary patch literal 3197 zcmbVOTXz#x6#h<|WI~uq4Hpffq6TO~VOjxEqDWgot0_eS#o~?Wbeaw%Gi7E{tT#kO z@qWMH{f_$PS~gT&x^yj{^auFnqi^yP)bC8Db||%0vsUJu+2@?udw=`e`|O|o`1(5l z8}a!9G@wyIOhp_CfenUJ(MGDepSMZA*6os3^j*s?<)wn8iUsoPs@zxgOxNa@<-$T0s$59P1<%+0 zswc(gsaPb%3#GVXdLFe)`S~i+SS*l~a@{R?0*h~tsO%d&Xc~T?a;b`CxImz3N@Zus zw0(g!1+%IRTK*Bee25-NWB75~uliQm)AsT;*qBctgXIb?Qn3OTQ%&HP+@$TXDyF+f z_YTqTYc1RIw+b}2wDzZPDOM`DOvUB6f{G$Ya$0e0t**lw)jEfUTza)v>Lh3-)8B3C z=<7R;#O(J zZhA%Yh;uMIgB-Lf@8!LTv`83j_kD5+k=J{7lNfThDTeVNVDZh@t< z#7G9Dh}%`%A@?uf{+>~ti7BObskmE8n<)*H6dZ&2JGNZZRSe3-6c^Agx%P}!3>N_4int_;^PJgndm6_4ULVV(wM zDkl?lQ)Yy2ssvHd<5bk4;0fkJ#J7+l4E&g7m|D+SBq`RVDLjRz6+ENjSv)6@iyRM8 zuS4UF$_q>@21AIDOoc8}X9!lEg-==cynM7WhL$eG3A~`HJE1wjP)QFc5srq}dG99Dx< z_>@6+O~Y}ArYn`ef&UrCS%MZaPV#lSFCl1Zk8PW7w`8U1VI^Cq4BNd#bF=L70FLa( zf|2c4FtXnYM)p_1$mWSB8u(v?f)T=+$B{#8B`JP)=Xukon{^VN7AC;2Au1}?6DUHn+L4QFy+LQLY4!Z*kc zoW|{Z`;+Z(bN)7$NMAWc96Q{B}Gx1EUh69=Sx*CjNt*M4#?oB1Wg*DKat`rAi zX{R_4PaiH$!aIqkOguea!+oc)Ed8L|Si?gnk)rf5O4BDM@#Ob7k%@nW=NlkVYJ^1T zt^jcjNwlLG9mM@gc9I*go^ARTc11g|8U5^CbmURNbr{1|9A$HI0^3-~IvJrZyoqjn z#P;F`+=!pigI{>v|H=#dck~75ZVvF}%xJ zjGFGjdw8Gm`GBWapy5w`JfWaL!7&Bd;K@l5?9LnUA@?-UijO(Q=)))YjI$KANH+2J E7aMPY&;S4c literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/manager/factory/AsyncFactory$2.class b/huacai-framework/target/classes/com/huacai/framework/manager/factory/AsyncFactory$2.class new file mode 100644 index 0000000000000000000000000000000000000000..85ce457f31a44a141cec4267026c1e0b44d2469c GIT binary patch literal 1201 zcma)6TTc@~6#k~ItZbKC0q=OBT($)n0a23@6EKoAsbVbj$(QMNC<}XOX119AEE7fa z!T9WtGM?QwVTnAn$?ndaoH^fjzWGl7{Qdb0z;nDBMFv>|ISV&1!m#OtK0odXN4We* z315B=)hYKyAlg##BjH4$>hT@j3!J_FW?_RNepi{SPpg=?iBSWlg*+^V=@W4#csFuAe&G629SD8OFtY6iZuFWVYnK`(#xY@F(!vx9 z48kzPU`& zx{~f_zj-2^sFKGVmMkp8W*Bn=B9+MQ#fGr$p&U_aFy!8ZElHZx+(5qX`pwt~O^+gl zSRSt-lpF5{!CZ9g(oJlo%Ma_?3YkiEsXwzGc9kRd+!$smZSA$#EJWsQ;CP`XiQdWR zIBXf%WLQs$IUJ;gExaH&C7n>UVox$`52;MwR9B~i8nCKCAXRcFQqyH;Xr=%*IT;T# ztuplFVS}NROnWAYnB|pkVCC;Hz9czidXG>J@ChsQwvx4pRXn8~!&(xFD;(1KIImd# zN(JaS$Jhm?&vCafcY#O0F&77aU@?Qs(&Na|5kW)S#5|l7L~#I-1*eP*o?)H7Ic(4$ N(@R>J1QxeIzklC1LTUg2 literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/manager/factory/AsyncFactory.class b/huacai-framework/target/classes/com/huacai/framework/manager/factory/AsyncFactory.class new file mode 100644 index 0000000000000000000000000000000000000000..5264b5f83af6730a6230764de853b19411f6a695 GIT binary patch literal 2038 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 literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/security/handle/AuthenticationEntryPointImpl.class b/huacai-framework/target/classes/com/huacai/framework/security/handle/AuthenticationEntryPointImpl.class new file mode 100644 index 0000000000000000000000000000000000000000..4e7e3dded713cc20ff31dc252ca7123b8571d962 GIT binary patch literal 1875 zcmbVNOLG)e6#ni!(lKcShKCL+5W-^;kX8y&X$X!(kcm^o%9yFLT;1v1%ygXYJKnwx ziIxl0rB>m>q6!zfz-_1$T0}tI`v?35hH=F|usl6Ik&zig%0>5m^f}*m&pF?@{o7x6 ze*th3r6DAcG>|fp#sI^%1%8=Z4er;h>FR=TG{e9d*LU>99RY48KheQjOTx>?p-hru9*80^-qJ8LVSww8bW^ZN3``!_!Rf;_igLP&)4o=8-|H32A(#t72Ak4I@+QY2z9w3v{fgf zm@VFz5g&#kp!0623Ef%D&J-DT=Eh2oqAF|U`Zaq98SF5y)5I?9W*F^_DAaBvuwwJs zXik}!m&)Tr@p`To@%^r?ICXYk-e-|Du*bx6c%C8G%j-xbtU2j%*SGQud~rquVS_l+ zLMf>j&gF_-Or3DXN}C0;hp`v?4D2^Cf&&bDB1hhEtGvpsc^>G6K>8=Fsq*wC22;yu z5|c0-?hbpS7!Tr*fx{+_Ajfd9_h_Bm#)a!7*Hprv6Kb97Y|OP$y@Mu+PS}02K{6d@ z$afLXEAELaQhh-BaYE(lC9BT;xrVUvp{@&GyAIc`^e26-mfn%BuZv!@Vc-}QTdJBB zG$YycLAfHT{|RB>ILWWAJy%*~pfIP9Q<4tha6%*C&Spy2T@q}eTNZr|zljvs z41<(MPxubefBEF9w7W1a%4UkUVRGBO@K}Ks9np*`Wsg-D47J|%N7^#jh%r7Gg`-5Q z9BRKOI(0Ng#i8i!(*Hj#RnTcykkOTt^nw~grdPQPLzC+P1_s_@*s(63;`BdX$f_(u z<%kPzB&CtZGW0JAACLSQvNIuR+!GZya7iY4-(86|d2XdJU%uByzsi1_E;1*;l7hnWs`eBk^AH=ua4CD6eAs>GZK(scd=`&#dBw zRg6Bu&mcwqd&z$vve-|T&`6tWf|v=f!xhbJoN4pta26B98||IL8%UDxDDrrdkh1ji cDd0TqOw#uh*bi!@K5h$$MQc-{v72DwZ=VE_OC literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/security/handle/LogoutSuccessHandlerImpl.class b/huacai-framework/target/classes/com/huacai/framework/security/handle/LogoutSuccessHandlerImpl.class new file mode 100644 index 0000000000000000000000000000000000000000..fbe505c689f450c02a1d2dd5d729bd68d100ea5a GIT binary patch literal 2780 zcmb7GZBrCS5Pk*@7LHX6XyO?)FDh^l7%|2urw~FUpa&!zT#V+$xxL{SxZ6E( zXST$1!Rx}U@s-M!bTmWvUF9l0&CofLFZZGcy%w@I`d~BMbG!!M422^U-|$64zVQ5Q z9!Mwjm43-Lg>JeND3FL~8#2<7sISCw^Rbu5`+p2ax}Z`*hW=NYb~U?!(St?B_UX0FE~9hXA>aTXWwu7!&>F5&V)>rV#7&_~uA!(9p+!`YF1WPPk7 zMnb?Z;C&k(81X%{J#xNr`V|{jag97khPLcHgfo$mFK01~5es=6qZnhzr_A5yOhdS$ zCVf5|ymXzVc*4+Opp6r>H$GFaFwSr_wZG*HH_*b>e9nspK@T{wJ0@ojCK(1pa>qnH zV(UZ@(>05aanr&lHg4h5wu0l>EfKZJfFyWUBc3wcNrx_R$&-|)<6B0Q+P`=$=ikAU zg@TQ{xW_P=GU=$>HiV-+|7GmH`FKLN*_Xu2Y7`;mZg@oUr)}^Y)qQm3zcCy8`I>4- ze@z72#F=5t*!T>y43^xXRG=w_=M%MuHXCxx;R_4*ZOr3KhLMz&t-$Tytl2HGvnqqI zPI<{nBE&G=#-;<5IaX4k4~uwU;h~KpmKZJ=2%@ekq9XW)2=rFqxi|QO(#mp_u*@J1 z5yMbg^KqeE!B-Z(wy}!RF(gXzOhO{tmu^-1uXWvvbsd+Uf+lfW7LG2W`?Y&UGtQHtDbCfSf_8Gfx&XdQ*|U znf4i?W!njrh}8CbZk!5|I>qqQ$qtX}LFDf@iAy`5v>-+mgdCCbe<5F{#A~wBa+IV8 zjz*U5fghJ8hT#)|#CS=jFJrFIjXUvB!YYQ-2id`JqivLrwTKEEk?M8HJy7u5+|t99 zl;KoK3uk*tG?Vye@9#LWX~-H_pi(&)PwYA*wTrGDrPcfUS&4#LlesnPkR7!j3!NssfUx#@^QD3H)SU2_G zP;dUxLyX!-G~?7W>bpi?f&CKw{+3$v8@Pza)EWV_0Z;H9^$g$R2NGfq{}l-WN~6ER z8r#LGsZ1`jhryq5J(t-v+#NF*(On=l`+r#I3 zSlGj|fzk;BF?&?~yH literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/web/domain/Server.class b/huacai-framework/target/classes/com/huacai/framework/web/domain/Server.class new file mode 100644 index 0000000000000000000000000000000000000000..9ab2b207e721c02e73ffc74898b498167bd79b18 GIT binary patch literal 8221 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 O4N+$&+GTY0%>M_EI2RQF literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/web/domain/server/Cpu.class b/huacai-framework/target/classes/com/huacai/framework/web/domain/server/Cpu.class new file mode 100644 index 0000000000000000000000000000000000000000..370c4ff6d9acfbbbe087e620432ac95d27a50c25 GIT binary patch literal 1608 zcmaizeNWR+6vm&sSD3}2PJBTWL}8mq6%j>nV3;H`L`*;o{$bK>1&Z5Bx)#EBGBG9) ziGTP3{7}Z{bPdd<%eJKF+~=Ni&+nYx{`vdsH-K3zrw~CjiI|2s5(I)?JhGjy`jJ`It9IS6 z8hX=o4oyd2Iq-N;<-mLI)fH4pj=8KMjX{ODYr95`YB90AO(_0S$l!VsLmF=2rou>D z6}ol1p?j`XYwB+t%iSYb-D7?{TP)^^l6qUiFz!%sY_C!EcS^aU)V!MR=g$p)VX6mMg-fhO95_IHt+K6}w84Ub7k|dnudFrctg@xw>XojM|pr zSh5bpn7d~+6{gnO8t5^m1%>`y)8)YYQE}!aD$NTemM!JB6#5vn*%};UiWC?V2(fHY zLJ|zwIA;9>w|pl_st<#b^v@c%u~p5rPC_fc>X8U4?ir=r)?%J%eY#S@Vd|1Jd~u3}kTS4A^_E<#UAW{b2-nL{uF)P`??Soe z!?{K~xz@?`p$nH>P2qYK$~E4DYcrH{dA%)`oH1fv=f5=o>4&OKEJ zO5amXLw~sa)U5jgsXLZq`HKRh*<3Y^F{CwQB+ftz6gJdjB7YAaRCzomm+dBv|UGU`_^7d zU-B%!Nd!CA0Rd$VV?Adu%0-E{m}AsdM?8O9;vJqRc0JQf-P`JWc~9bfe84N)gpeeO zoRPt0e5m0giH~tb;7b4OwsKU%v`xp?OW|-;)qU6dg@V*D{YP!bw`{ZYc-d4A?ak(j zvD;6qE1W?VISqMFQ6DQo1ea~_l1wQz!gpL~m8b!NhxkPk6 z!V_8eiNqW)Of+5FOyg55X!uOx7TczWh2K!VxygwTxUfaPTB%esjM~~756a~X$$TzR zz#Vc+wB;b_WnXzd?j1N_>HP0&ngsuVu2i;u5OBLiRL#*U}e=#=Cbu z)?AiYVU7%OY`BLk%H}U6R&ifIa?%W|^5jFX#nXTR+) zd5t#L3x(+9X^Z5HPCJ5<#0DpQ%;!T=;wT9#?ha9$En1Gb(YCiuucEg1IABG$uJ)?R zvv}S!Cj6#F@-xLqnqJd%1#(A(m2Trv0~=&$g@#1d$W^JKXh}&O(SSY$1C1=J)IwBH zWa^E`w%Iawx{{kMY7qKN#%^hHz^li_m!VzZWHKh3?3VYgQ5ANbbVrhGLE%&=Pu(n1 z2X!a>?KsvHhfLXRdv$Zw;((3O4KvH#glX!A5$gLSJ?W03(ForW!JF=e;7zR(yr~oD zJr24MkfP^9n(6cw^iJmgg7_m?_BB2EGidua!82HZM<~-wpn^?$^5z8+3fASHL7TuE z$qvr`M)nc(HF6OR8ERDVZBJtnqx44}^H-lk>l1Jd0kh;m4-_s2^HfiP3Hi7T|2URs zatO~{EYDUv&*UJ_E#kR7gone4B_GQGtUnpPobBu zan2Y0Pc?=>-H$+xhe`V}6k@lW_^D8c2O6b+0`GQkX|M>25xuEU?+Io|g$rpe00q(= zO#h9m0>6_nT1AzJKspqtg#!IDeunFVo3|pHli}u{pnBdH$rTq{Du3-ct`COz37xeT zD4Z@q?ko=Rb}Y!vL6CYZNIf3p<{(IuK&&Ak+}aGX6N1dskvK(}KQZ6I?GEk^((FfQ uGGSQE(+V9d1q#NX!cnZkZm6&jFws2{q;*Z;1&V_jzeY6r_d_GdfAc?rX11>Y literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/web/domain/server/Mem.class b/huacai-framework/target/classes/com/huacai/framework/web/domain/server/Mem.class new file mode 100644 index 0000000000000000000000000000000000000000..7cc52becbad45e61999182b4ca16083e8d2d5e31 GIT binary patch literal 1094 zcmaiyU2hUW6o%j7Ybma2Ezs5)to5sav}28_7xk-=G_j$2VN2rW0@JdUWt07&|G__F zOf)uL_yfH17a8Ay4G_XcF3!w3JM+$S&dcw=KYsyuiYGZFkTj4okw!*f>B1UVvS;*js7u%TBrjD(D2$ZJexOLpV3 z0|J%n^|zI}Mp-ejsyR}juR6ML)5Q9)aONqMdHr$n%xx1JxI+ymBtx1Sl{zoJaXU(2 zv0?YsyU=MV@5E~LSXpkkZL8O`JX_~cF%@)eU!c~Q;`~qB`vTc>6`YJ(=Lq>|)*lyA z<)ccIBRLW@K7JtS!37XpRoxJ{~X=c!(`VeR+>U z`kYz)2BU!M=_@RLVV=N5qco*qjy1}tL>jM%n&cg$y7Botj0uANcY;D03TTX#VQxec zyukjd6cKnd7pNEmv^5u~76VkA1*#EfI|gVq0@|Jnv=#%jI~Qmt254;-Xpcb8Vu02o kAUOtl5m}a}Wkqe9LC*@fb%oNbbNf?V`LT14hyL#V1ADreBme*a literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/web/domain/server/Sys.class b/huacai-framework/target/classes/com/huacai/framework/web/domain/server/Sys.class new file mode 100644 index 0000000000000000000000000000000000000000..96c01a95a72e8a254a688e34ff452f6142f23f26 GIT binary patch literal 1353 zcma))+iuf95QhIvlelpm8hQxlQ%fOnDtuEeP*D&HLPQF!L?HK$yCg1&9c}DZycI|w zBrbRW9ttsIho*Hm5;r?L$NhF@e*gLT3&0+BP2{j-Aa9|7qCn-?J#n3p8w{Mo{;~8T zf#P#N@S~={Qmx)AVHqU@riB$)0=5^9onh>HuJ3$Q?pU6N>XUOS`_54~cKyJaN_8TY z)BQXZuqb#EM^YV7puk#Z{#G|qelXbQ>Q^kRa%uBlbbG?)s}|PSY{XRZm9N;gVPTVP z#c;|c*>ugq^_l6V@`eKWR(M2@t#n9mRPWvXD0x2g+)>X}KI=1MJ{tN&y4(5x zf9+H8fs9%g{l*1*wdz@_VjLc!&rIpwa-mira?X7vETtUGe;{@pp!;Tw;azUku-=zQu7l@9VfX0 z!8=6f4bmKvf@Hz|0r52nyGbXzL8WieX{7R;&lNasAcNp`hdD8)#7#m52Y8o|rp3(T9OueNADQ7_(Ey%VWq?`rWBao*HK)BHi z@=yy>$$~u7g6!x)Dp`;wfxK7%!joo@M_Q2eEJ#ZW@>mbDo&|YBAngSpylMH~ymQZccBFZyuWo|c8Si@RG$Kb4w5 zOnmSI_@Rvd+1;kNoIa3Ec4q%)=A7ToocZ(j*KZ!G|oF`y{@;@JyIOZ`W2HV zSs(V#iPI*{u$b~4Up6`Hs!7*aN!DEzW==nba^ zcf4e`)pn;$gko=yYF>HjI_6h_6S^o)HMD&-lSnhuf^BPwS<>ZCXHK1;xYG@L~$b+_v!i8Gs zarSOlJKP9VkN1d#WNwr3GG(XZR#n(?Giv@a_nYpZ1qoa|18-a*cnNNJVA0^fqNjmH zCj*NH1{QsT9Vax8(s(c6%YqfKslrbZKj0*A1O>~4qwc~t!vMNR_wgm@0X@Wf0e(OE zl#K5X&L>;ivJnTUgIXh9z7K_=oLZvpad00?)UK^|*Crs5#)wIIuSkf}Jx zM}U+Efbi905K9X(8wdHM1zFL9%*H`J1LVs95Wf2i@EF& literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/web/exception/GlobalExceptionHandler.class b/huacai-framework/target/classes/com/huacai/framework/web/exception/GlobalExceptionHandler.class new file mode 100644 index 0000000000000000000000000000000000000000..905be26785b0ee1b74901fc7cd66ac8a35e766fe GIT binary patch literal 7232 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 literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/web/service/PermissionService.class b/huacai-framework/target/classes/com/huacai/framework/web/service/PermissionService.class new file mode 100644 index 0000000000000000000000000000000000000000..80ac4ca503094fa4c5f56f56be718f9815364f28 GIT binary patch literal 3529 zcmb7`OLG)e7>3`InVyqOItBv?jF)f~GPyNIjG7=9K|mRzFeWM}rZZ`i4n5s*x+fUC z-xU?L$_-RmR=Hur#!Unllq(lj`2*az^FOGi;(NM#ZZLSMRC-RIKIgkU-+R99AO8CG zDu5mMG>sM{B$7Hbqy$zim`kSNnC_hM*vx`e2n13)ZPyNV2_*WmQ)#rKO(LzM9lF5g zf>$)=%Vxo}4GxQ*Ym@`q@r`^?vfa5!H5QPz|8Q|JSQhB+%heo2YsRz3)3^nl603CF ziY|e>ZfevjluLH79GlhWtRUyj+3uuol?3kY%jRy}S@24hG3ymg+ck>btmPQhE#oSg zH9ES{!z^uoqU<==g%~jiLtKZ9#BDlm#~lJg|I3lUEe!Q>t5mdo-}YQ0xs}0&BorSXeZ{7iJ^w3YEQ$cH)D&$ zT{^a6o4|v0QP+(6F;y1O4XhUfqZa7CXy%aT%qqfZ-wNWX>o`?PNquC|FEOBF5JLhZ zO+B8rW(<~W$u3xR&hpU=Va=OUa=5L6S0v z#Jc@o#cH_$rEcVxeYJz~*n_acm$7G-Ncm^x-qV3cfWMS-o&>fWsESy(!rS8`ODtH@e0Phb6G{ALy2*l`FAqp?IXf9M(`^_+Qelojp}^QcWr&3Y|(;%~f+%H_S&<3;5fXx940l zD3`e6{_A>P)5vekCaXlB_sXS$b--4hdTO0CZm`3utOA*1WpdD<;6AY8t%!tTxFv?bFi(@%@I>)?T*2DQ zxHFvQ^G*F~V&EI}UE+>J=%p8u<2!j>k2Z$bflYkVZovk=6SrVn=zT-vA@)vR@Db2| z2iKRe#CT8BMjDB>Kd?q(ol57$YJ?MfuF~%8A6UU~@=M6%MI;lo5TZm!LRUS}(yFV@ z*p+gip`~Mwvb|ifE$(1st$Z~7U$R%pPYiV3vx0lal9}YyQ7ti=>P~fQUtwb=*_|30 zlNtFv;~dIJG9tC)rMFY!;i~HjWQh;)`Y5K@ zz@OsyBwN0!;0}g=kRNc0oFYD5O>r7$Vw(AwW)}D1CA`esUg1hr>1gIv&L|LDk*bis z#@DBm$V!Y7yu^aUYl6Dz{gwWN>aV;~CqfczihJjqkXl7D<15%*kxG1f1rG~M3|+xt zf%DkT!BK(lFsb0J;K?zq>qJId!Lt|8nbBx=n&S}K#cMy-m3Nq5Y`}45{S1knB(dkH z8xxyRAU6w#8KsGN-Ysx+s!HoHQ+tDCG_G2YH}MvdD*h_BdAuESJ5i^?^^jzO(ep2aSFM7^Ek zC(BtX@Ut+3eiGcpYN)uC7LHa~9>sex%l&mMdqSOsEdN4BsI0Y0S1s!6l~>h&&hS_D zpWcD4xht?+Z~?0Z*nXzNoAY&tJvu+pVgDkGC+aJ`)hK#t?PV5-*1~hrQq{kjIfGaE iOwmpuu`aPkFjLhlKj8n_aP%Q_2J;j6nE&T|eDXKb>rKM| literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/web/service/SysLoginService.class b/huacai-framework/target/classes/com/huacai/framework/web/service/SysLoginService.class new file mode 100644 index 0000000000000000000000000000000000000000..766c1ecad1d3534f746ca21c526fdfc7f300eddd GIT binary patch literal 6511 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 F{{aa9u#f-% literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/web/service/SysPermissionService.class b/huacai-framework/target/classes/com/huacai/framework/web/service/SysPermissionService.class new file mode 100644 index 0000000000000000000000000000000000000000..2df06bfefab02105bc1cb5dc4dc4d66764b51291 GIT binary patch literal 2635 zcmbtWU31e$6g}(2mXin)624q$8wxFUFj1jULL34~3MAAC5R*{or($m$fhD<;OzNi& zKZ8E?jaNF;2~9iG=`%lx>DO7wUxai>rlT22dv*7od(S<)s~3O&`a6JgSnov)T^hP| z#E}pfcwjyp1lw8}i978&R71WJF-Cy#hC!ksWRLQoS5a)gp z`vl^qGW1~x`!yWUaS(?D_O!tXOmDMP5;%~~Z_PZHx!;2n-qLVH$5Fg3aK0m>RnN5L zy6ZhO*5!)93?5lUsj|5xJ=^kq%XJEoQJ~LrE3!2@mG21S*Zn}+t@(2du-JCaDN5ee zF^ppZJ!KiFQs&0Z{Bsvxjr1n2@UV-_y8vbGCKwlIO@v^Tcv>8*-zJ-V8=SS z39%aBVFyVfv4@b+F@jTUH~E~ZR0L9uX`$DQTTu{Xg|sGdn#~y*=cNiXbbN?20!bFA za_~Fq3n%9-KOhw!;hcu^IzCn~Y1btq z34y(BAt(UiTY>bxv(x$~hf3@F}HbOQ*JDY0bBt$>%l9>sV+?OGTb5_2Cw7 zYgp7#*nx^V;t3pzRHST5MgDX>3aNmWbS&d8IrnAImRQ*6NXH@1N=lh!mBKw8_Z9x@ zF`J@HE!MaJ*mBwOt(A(LavV1>mA)8sEWe#NR;20p#;RFlhwFyfnqW-T0(af=WGTl^ z&A25g(4V&)xlprLq<6<;c=kH)7R}1C=~-&rFm?xP7V$20uIRQJ-?T9hZeK(4ZB7I( zr#r*Fap%izU5qTE#%)4iba!#MB)w?$^>673*TbvT*Q-qij=$Mgi6uPCosD!0^jhsmhgas)=URXC3lPr3oSW6ZdA)pdAA z=5UPLMU01aj2DkF2z<^_g3m8PEBBEtwl0o&n)Ci8Jq$htpYf6B5I==ezT#bd2KM7? z-u1`<-@xQZU-T`#YO> zFNRH|Y0f_5q;4pngUkgufg}&Mexz{(S>`kv0uM*Q8^DA?36cpUk-;j;Ok$05ReF)d zqU8ZCJ&65@BN~Q=@Q}k8Ds-`%S&Mmw)o6eGTGrWS)@3F5@y-YIaYszB`x54+zuIhN0pDVC!c7Gp#+ecF;xUQJF9BOY=37lt&9X_$P8vDi!g8Z;@6 NAqdH?wG{XW_!rw(x&i`bZ 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 literal 0 HcmV?d00001 diff --git a/huacai-framework/target/classes/com/huacai/framework/web/service/UserDetailsServiceImpl.class b/huacai-framework/target/classes/com/huacai/framework/web/service/UserDetailsServiceImpl.class new file mode 100644 index 0000000000000000000000000000000000000000..06f1010ae7cb9dc36ffdf6574c5ed2c4e03aa915 GIT binary patch literal 3388 zcmbVO>2nlS5dXa-Fq_RFuyTb%ZpmiD4p)S@TnU?CbQ6$36!2hn_GOu5c4nEGO^Aq! z2jYPzREfn0%kl$NmQ{*Y5=Hd8e}@tMd=*2_k4>m@hJ(G%YvD&+3lrMA3|dgccc_k!08~ z)1XgT4|g0j&jZe+g8$0URt4_KDtJSpAgCxdVv8vFtrgTy<(|rXJ9bEDmGKC6GOU}4 zjhm%{<5hQ`tGXr1rj+i?bRO+YQz%txr&6CPW;@zsJc<-8&2#sFm7`Kz)D*9VcMtYT z*e7GZ(5^!LzgH$hl#Hjj(PgOlC^~RJLRv_M-LLn4BD zg5=W{!IS^x=)!fXX>!!ZAbXCohNTVhoY)%2Wc1@Hs;epILDey|hWW4FmT2D|4loDH zC?UsjLc&QI&)`{x?V+5{)&@glST*#V>QXAVHico3wR^OXcz=|ldS1pUs1%iEb23A_ zTsK9hVi>w#dw#Phigq>~fWx(tEjpK1GJ?~b-<`vqIwe74*c@V@3j6{UtRdQv4^ZTn z%NPLlDUn|Rx;sZb>#mBJF|nFQ&fSrevn zI-u~d5m~KVNZyJ#Bf*y8fW}#CD5}|8AxIQc5AEV<2Mqh}J0;$n5q*t%q)X7U7#59a zgyB@Uoq05c8iD^FLS7{w6%)x{U3Mj^YZNa?I49#poM%{CIS0B_&9&@NKYRXkVb~Z( zb-v9w49RdL_bwcU zdDNS=p&qpuFcK{fa!0}k3xgL8>L$ zmFDjtCBDbJ8uYYG;K6HHeG`qbRU}$ni(A-uJhrL!HfkmiABXbuO>7&(u9h+E9z%Pe z+ZED%>@MBMechgE-NPZ>BOzTvag?k+-AtF~z9w9?5V35d2!JM{ERGlwScMiO>3=V_ zpc`9pf+qbST2Z2d(nYl4J*2$2*Z8poaZ{9d3vW~W4Rn78?-B=>$!-+2e<3MhPa;7p zL5wiGPd}P~bQKi$Kq6hB@fNbj6JyBH?{tE|4$*@(j*@^{<3aUl-|x9!;x8(@Z}gl} nz2U=h_STXM@mC* + + + huacai + com.huacai + 3.8.7 + + 4.0.0 + + huacai-generator + + + generator代码生成 + + + + + + + org.apache.velocity + velocity-engine-core + + + + + commons-collections + commons-collections + + + + + com.huacai + huacai-common + + + + + diff --git a/huacai-generator/src/main/java/com/huacai/generator/config/GenConfig.java b/huacai-generator/src/main/java/com/huacai/generator/config/GenConfig.java new file mode 100644 index 0000000..cb63779 --- /dev/null +++ b/huacai-generator/src/main/java/com/huacai/generator/config/GenConfig.java @@ -0,0 +1,116 @@ +package com.huacai.generator.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; + +/** + * 读取代码生成相关配置 + * 用于加载代码生成器所需的配置参数(如作者、包路径、表前缀等) + * + * @author huacai + */ +@Component +// 指定配置属性的前缀为"gen",用于绑定配置文件中以"gen."开头的属性 +@ConfigurationProperties(prefix = "gen") +// 指定配置文件的位置为类路径下的generator.yml +@PropertySource(value = { "classpath:generator.yml" }) +public class GenConfig +{ + /** 作者(代码生成时注释中的作者信息) */ + public static String author; + + /** 生成代码的包路径(基础包结构) */ + public static String packageName; + + /** 是否自动去除表前缀(生成类名时是否排除表前缀),默认false */ + public static boolean autoRemovePre; + + /** 表前缀(生成类名时需要排除的前缀,多个前缀可用逗号分隔) */ + public static String tablePrefix; + + /** + * 获取作者信息 + * + * @return 作者姓名 + */ + public static String getAuthor() + { + return author; + } + + /** + * 设置作者信息(从配置文件注入) + * + * @param author 作者姓名 + */ + @Value("${author}") + public void setAuthor(String author) + { + GenConfig.author = author; + } + + /** + * 获取生成代码的包路径 + * + * @return 包路径(如"com.huacai.modules.system") + */ + public static String getPackageName() + { + return packageName; + } + + /** + * 设置生成代码的包路径(从配置文件注入) + * + * @param packageName 包路径 + */ + @Value("${packageName}") + public void setPackageName(String packageName) + { + GenConfig.packageName = packageName; + } + + /** + * 获取是否自动去除表前缀的标识 + * + * @return true-自动去除;false-保留 + */ + public static boolean getAutoRemovePre() + { + return autoRemovePre; + } + + /** + * 设置是否自动去除表前缀(从配置文件注入) + * + * @param autoRemovePre 自动去除标识 + */ + @Value("${autoRemovePre}") + public void setAutoRemovePre(boolean autoRemovePre) + { + GenConfig.autoRemovePre = autoRemovePre; + } + + /** + * 获取表前缀 + * + * @return 表前缀字符串(如"sys_") + */ + public static String getTablePrefix() + { + return tablePrefix; + } + + /** + * 设置表前缀(从配置文件注入) + * + * @param tablePrefix 表前缀 + */ + @Value("${tablePrefix}") + public void setTablePrefix(String tablePrefix) + { + GenConfig.tablePrefix = tablePrefix; + } +} \ No newline at end of file diff --git a/huacai-generator/src/main/java/com/huacai/generator/controller/GenController.java b/huacai-generator/src/main/java/com/huacai/generator/controller/GenController.java new file mode 100644 index 0000000..368246b --- /dev/null +++ b/huacai-generator/src/main/java/com/huacai/generator/controller/GenController.java @@ -0,0 +1,298 @@ +package com.huacai.generator.controller; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.huacai.common.annotation.Log; +import com.huacai.common.core.controller.BaseController; +import com.huacai.common.core.domain.AjaxResult; +import com.huacai.common.core.page.TableDataInfo; +import com.huacai.common.core.text.Convert; +import com.huacai.common.enums.BusinessType; +import com.huacai.generator.domain.GenTable; +import com.huacai.generator.domain.GenTableColumn; +import com.huacai.generator.service.IGenTableColumnService; +import com.huacai.generator.service.IGenTableService; + +/** + * 代码生成控制器 + * 提供代码生成相关的CRUD操作、表结构导入、代码预览、生成及下载等功能 + * + * @author huacai + */ +@RestController +@RequestMapping("/tool/gen") +public class GenController extends BaseController +{ + // 注入代码生成表服务,处理表结构相关业务逻辑 + @Autowired + private IGenTableService genTableService; + + // 注入代码生成表字段服务,处理表字段相关业务逻辑 + @Autowired + private IGenTableColumnService genTableColumnService; + + /** + * 查询代码生成列表 + * 分页查询已导入的表结构列表 + * + * @param genTable 代码生成表查询条件对象 + * @return 分页表格数据(包含表结构列表) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/list") + public TableDataInfo genList(GenTable genTable) + { + // 开启分页 + startPage(); + // 查询表结构列表 + List list = genTableService.selectGenTableList(genTable); + // 返回分页数据 + return getDataTable(list); + } + + /** + * 获取代码生成业务详情 + * 根据表ID查询表结构详情、字段列表及所有表列表(用于关联表选择) + * + * @param tableId 表ID + * @return 包含表信息、字段列表和所有表列表的响应对象 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:query')") + @GetMapping(value = "/{tableId}") + public AjaxResult getInfo(@PathVariable Long tableId) + { + // 查询表结构详情 + GenTable table = genTableService.selectGenTableById(tableId); + // 查询所有表结构(用于关联表选择) + List tables = genTableService.selectGenTableAll(); + // 查询该表的字段列表 + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + // 封装返回数据 + Map map = new HashMap(); + map.put("info", table); + map.put("rows", list); + map.put("tables", tables); + return success(map); + } + + /** + * 查询数据库表列表 + * 分页查询数据库中可导入的表结构列表 + * + * @param genTable 代码生成表查询条件对象 + * @return 分页表格数据(包含数据库表列表) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/db/list") + public TableDataInfo dataList(GenTable genTable) + { + // 开启分页 + startPage(); + // 查询数据库表列表 + List list = genTableService.selectDbTableList(genTable); + // 返回分页数据 + return getDataTable(list); + } + + /** + * 查询数据表字段列表 + * 根据表ID查询该表的所有字段信息 + * + * @param tableId 表ID + * @return 包含字段列表的表格数据 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping(value = "/column/{tableId}") + public TableDataInfo columnList(Long tableId) + { + TableDataInfo dataInfo = new TableDataInfo(); + // 查询表字段列表 + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + dataInfo.setRows(list); + dataInfo.setTotal(list.size()); + return dataInfo; + } + + /** + * 导入表结构(保存) + * 将数据库表结构导入到代码生成器中 + * + * @param tables 表名列表字符串(逗号分隔) + * @return 操作结果 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:import')") + @Log(title = "代码生成", businessType = BusinessType.IMPORT) + @PostMapping("/importTable") + public AjaxResult importTableSave(String tables) + { + // 将表名字符串转换为数组 + String[] tableNames = Convert.toStrArray(tables); + // 查询表信息列表 + List tableList = genTableService.selectDbTableListByNames(tableNames); + // 导入表结构 + genTableService.importGenTable(tableList); + return success(); + } + + /** + * 修改保存代码生成业务 + * 更新代码生成表的配置信息 + * + * @param genTable 代码生成表对象(包含更新信息) + * @return 操作结果 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult editSave(@Validated @RequestBody GenTable genTable) + { + // 验证编辑参数 + genTableService.validateEdit(genTable); + // 更新表结构信息 + genTableService.updateGenTable(genTable); + return success(); + } + + /** + * 删除代码生成记录 + * 根据表ID批量删除已导入的表结构 + * + * @param tableIds 表ID数组 + * @return 操作结果 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:remove')") + @Log(title = "代码生成", businessType = BusinessType.DELETE) + @DeleteMapping("/{tableIds}") + public AjaxResult remove(@PathVariable Long[] tableIds) + { + genTableService.deleteGenTableByIds(tableIds); + return success(); + } + + /** + * 预览代码 + * 根据表ID生成并预览各类型代码(如实体类、Controller、Service等) + * + * @param tableId 表ID + * @return 包含各类型代码的响应对象 + * @throws IOException IO异常 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:preview')") + @GetMapping("/preview/{tableId}") + public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException + { + // 生成代码并返回(key为文件名,value为代码内容) + Map dataMap = genTableService.previewCode(tableId); + return success(dataMap); + } + + /** + * 生成代码(下载方式) + * 根据表名生成代码并打包为ZIP文件供下载 + * + * @param response HTTP响应对象 + * @param tableName 表名 + * @throws IOException IO异常 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/download/{tableName}") + public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException + { + // 生成代码字节数组(ZIP格式) + byte[] data = genTableService.downloadCode(tableName); + // 输出ZIP文件到响应流 + genCode(response, data); + } + + /** + * 生成代码(自定义路径) + * 根据表名生成代码并输出到配置的自定义路径 + * + * @param tableName 表名 + * @return 操作结果 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/genCode/{tableName}") + public AjaxResult genCode(@PathVariable("tableName") String tableName) + { + genTableService.generatorCode(tableName); + return success(); + } + + /** + * 同步数据库 + * 将数据库表结构的最新变化同步到代码生成器中 + * + * @param tableName 表名 + * @return 操作结果 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @GetMapping("/synchDb/{tableName}") + public AjaxResult synchDb(@PathVariable("tableName") String tableName) + { + genTableService.synchDb(tableName); + return success(); + } + + /** + * 批量生成代码 + * 根据表名列表批量生成代码并打包为ZIP文件供下载 + * + * @param response HTTP响应对象 + * @param tables 表名列表字符串(逗号分隔) + * @throws IOException IO异常 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/batchGenCode") + public void batchGenCode(HttpServletResponse response, String tables) throws IOException + { + // 将表名字符串转换为数组 + String[] tableNames = Convert.toStrArray(tables); + // 批量生成代码字节数组(ZIP格式) + byte[] data = genTableService.downloadCode(tableNames); + // 输出ZIP文件到响应流 + genCode(response, data); + } + + /** + * 生成ZIP文件并输出到响应流 + * 设置响应头信息,将代码字节数组以ZIP文件形式返回给客户端 + * + * @param response HTTP响应对象 + * @param data 代码字节数组(ZIP格式) + * @throws IOException IO异常 + */ + private void genCode(HttpServletResponse response, byte[] data) throws IOException + { + response.reset(); + // 设置跨域相关响应头 + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); + // 设置文件下载响应头 + response.setHeader("Content-Disposition", "attachment; filename=\"huacai.zip\""); + response.addHeader("Content-Length", "" + data.length); + // 设置内容类型为二进制流 + response.setContentType("application/octet-stream; charset=UTF-8"); + // 将字节数组写入响应输出流 + IOUtils.write(data, response.getOutputStream()); + } +} diff --git a/huacai-generator/src/main/java/com/huacai/generator/domain/GenTable.java b/huacai-generator/src/main/java/com/huacai/generator/domain/GenTable.java new file mode 100644 index 0000000..e777c28 --- /dev/null +++ b/huacai-generator/src/main/java/com/huacai/generator/domain/GenTable.java @@ -0,0 +1,437 @@ +package com.huacai.generator.domain; + +import java.util.List; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import org.apache.commons.lang3.ArrayUtils; +import com.huacai.common.constant.GenConstants; +import com.huacai.common.core.domain.BaseEntity; +import com.huacai.common.utils.StringUtils; + +/** + * 业务表实体类(对应数据库表gen_table) + * 存储代码生成器所需的表结构配置信息,如表名、实体类名、生成路径等 + * + * @author huacai + */ +public class GenTable extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 编号(主键ID) */ + private Long tableId; + + /** 表名称 */ + @NotBlank(message = "表名称不能为空") + private String tableName; + + /** 表描述 */ + @NotBlank(message = "表描述不能为空") + private String tableComment; + + /** 关联父表的表名(用于主子表生成) */ + private String subTableName; + + /** 本表关联父表的外键名(用于主子表生成) */ + private String subTableFkName; + + /** 实体类名称(首字母大写) */ + @NotBlank(message = "实体类名称不能为空") + private String className; + + /** 使用的模板类型(crud单表操作、tree树表操作、sub主子表操作) */ + private String tplCategory; + + /** 前端类型(如element-ui模版、element-plus模版) */ + private String tplWebType; + + /** 生成包路径 */ + @NotBlank(message = "生成包路径不能为空") + private String packageName; + + /** 生成模块名 */ + @NotBlank(message = "生成模块名不能为空") + private String moduleName; + + /** 生成业务名 */ + @NotBlank(message = "生成业务名不能为空") + private String businessName; + + /** 生成功能名 */ + @NotBlank(message = "生成功能名不能为空") + private String functionName; + + /** 生成作者 */ + @NotBlank(message = "作者不能为空") + private String functionAuthor; + + /** 生成代码方式(0-zip压缩包、1-自定义路径) */ + private String genType; + + /** 生成路径(不填默认项目路径) */ + private String genPath; + + /** 主键信息(对应的表字段对象) */ + private GenTableColumn pkColumn; + + /** 子表信息(用于主子表生成时关联子表) */ + private GenTable subTable; + + /** 表列信息列表(该表所有字段的配置) */ + @Valid + private List columns; + + /** 其它生成选项(存储额外的生成配置) */ + private String options; + + /** 树编码字段(树表生成时使用) */ + private String treeCode; + + /** 树父编码字段(树表生成时使用) */ + private String treeParentCode; + + /** 树名称字段(树表生成时使用) */ + private String treeName; + + /** 上级菜单ID字段(用于关联菜单) */ + private String parentMenuId; + + /** 上级菜单名称字段(用于关联菜单) */ + private String parentMenuName; + + // 以下为getter和setter方法 + + public Long getTableId() + { + return tableId; + } + + public void setTableId(Long tableId) + { + this.tableId = tableId; + } + + public String getTableName() + { + return tableName; + } + + public void setTableName(String tableName) + { + this.tableName = tableName; + } + + public String getTableComment() + { + return tableComment; + } + + public void setTableComment(String tableComment) + { + this.tableComment = tableComment; + } + + public String getSubTableName() + { + return subTableName; + } + + public void setSubTableName(String subTableName) + { + this.subTableName = subTableName; + } + + public String getSubTableFkName() + { + return subTableFkName; + } + + public void setSubTableFkName(String subTableFkName) + { + this.subTableFkName = subTableFkName; + } + + public String getClassName() + { + return className; + } + + public void setClassName(String className) + { + this.className = className; + } + + public String getTplCategory() + { + return tplCategory; + } + + public void setTplCategory(String tplCategory) + { + this.tplCategory = tplCategory; + } + + public String getTplWebType() + { + return tplWebType; + } + + public void setTplWebType(String tplWebType) + { + this.tplWebType = tplWebType; + } + + public String getPackageName() + { + return packageName; + } + + public void setPackageName(String packageName) + { + this.packageName = packageName; + } + + public String getModuleName() + { + return moduleName; + } + + public void setModuleName(String moduleName) + { + this.moduleName = moduleName; + } + + public String getBusinessName() + { + return businessName; + } + + public void setBusinessName(String businessName) + { + this.businessName = businessName; + } + + public String getFunctionName() + { + return functionName; + } + + public void setFunctionName(String functionName) + { + this.functionName = functionName; + } + + public String getFunctionAuthor() + { + return functionAuthor; + } + + public void setFunctionAuthor(String functionAuthor) + { + this.functionAuthor = functionAuthor; + } + + public String getGenType() + { + return genType; + } + + public void setGenType(String genType) + { + this.genType = genType; + } + + public String getGenPath() + { + return genPath; + } + + public void setGenPath(String genPath) + { + this.genPath = genPath; + } + + public GenTableColumn getPkColumn() + { + return pkColumn; + } + + public void setPkColumn(GenTableColumn pkColumn) + { + this.pkColumn = pkColumn; + } + + public GenTable getSubTable() + { + return subTable; + } + + public void setSubTable(GenTable subTable) + { + this.subTable = subTable; + } + + public List getColumns() + { + return columns; + } + + public void setColumns(List columns) + { + this.columns = columns; + } + + public String getOptions() + { + return options; + } + + public void setOptions(String options) + { + this.options = options; + } + + public String getTreeCode() + { + return treeCode; + } + + public void setTreeCode(String treeCode) + { + this.treeCode = treeCode; + } + + public String getTreeParentCode() + { + return treeParentCode; + } + + public void setTreeParentCode(String treeParentCode) + { + this.treeParentCode = treeParentCode; + } + + public String getTreeName() + { + return treeName; + } + + public void setTreeName(String treeName) + { + this.treeName = treeName; + } + + public String getParentMenuId() + { + return parentMenuId; + } + + public void setParentMenuId(String parentMenuId) + { + this.parentMenuId = parentMenuId; + } + + public String getParentMenuName() + { + return parentMenuName; + } + + public void setParentMenuName(String parentMenuName) + { + this.parentMenuName = parentMenuName; + } + + /** + * 判断当前表是否为子表(主子表模式) + * + * @return true-是子表;false-不是 + */ + public boolean isSub() + { + return isSub(this.tplCategory); + } + + /** + * 静态方法:判断模板类型是否为子表(主子表模式) + * + * @param tplCategory 模板类型 + * @return true-是子表模板;false-不是 + */ + public static boolean isSub(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory); + } + + /** + * 判断当前表是否为树表 + * + * @return true-是树表;false-不是 + */ + public boolean isTree() + { + return isTree(this.tplCategory); + } + + /** + * 静态方法:判断模板类型是否为树表 + * + * @param tplCategory 模板类型 + * @return true-是树表模板;false-不是 + */ + public static boolean isTree(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory); + } + + /** + * 判断当前表是否为普通CRUD表 + * + * @return true-是CRUD表;false-不是 + */ + public boolean isCrud() + { + return isCrud(this.tplCategory); + } + + /** + * 静态方法:判断模板类型是否为普通CRUD表 + * + * @param tplCategory 模板类型 + * @return true-是CRUD模板;false-不是 + */ + public static boolean isCrud(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory); + } + + /** + * 判断指定的Java字段是否为超级字段(基类中已定义的字段) + * + * @param javaField Java字段名 + * @return true-是超级字段;false-不是 + */ + public boolean isSuperColumn(String javaField) + { + return isSuperColumn(this.tplCategory, javaField); + } + + /** + * 静态方法:判断指定的Java字段是否为超级字段(基类中已定义的字段) + * 树表包含树基类和普通基类的字段,普通表仅包含普通基类的字段 + * + * @param tplCategory 模板类型 + * @param javaField Java字段名 + * @return true-是超级字段;false-不是 + */ + public static boolean isSuperColumn(String tplCategory, String javaField) + { + if (isTree(tplCategory)) + { + // 树表的超级字段包括树基类和普通基类的字段 + return StringUtils.equalsAnyIgnoreCase(javaField, + ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY)); + } + // 普通表的超级字段仅包括普通基类的字段 + return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY); + } +} \ No newline at end of file diff --git a/huacai-generator/src/main/java/com/huacai/generator/domain/GenTableColumn.java b/huacai-generator/src/main/java/com/huacai/generator/domain/GenTableColumn.java new file mode 100644 index 0000000..328d4d4 --- /dev/null +++ b/huacai-generator/src/main/java/com/huacai/generator/domain/GenTableColumn.java @@ -0,0 +1,492 @@ +package com.huacai.generator.domain; + +import javax.validation.constraints.NotBlank; +import com.huacai.common.core.domain.BaseEntity; +import com.huacai.common.utils.StringUtils; + +/** + * 代码生成业务字段表实体类(对应数据库表gen_table_column) + * 存储代码生成器所需的表字段配置信息,如字段名、Java类型、是否为主键等 + * + * @author huacai + */ +public class GenTableColumn extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 编号(主键ID) */ + private Long columnId; + + /** 归属表编号(关联gen_table的tableId) */ + private Long tableId; + + /** 数据库列名称 */ + private String columnName; + + /** 数据库列描述 */ + private String columnComment; + + /** 数据库列类型(如varchar、int等) */ + private String columnType; + + /** JAVA数据类型(如String、Integer等) */ + private String javaType; + + /** JAVA字段名(实体类中的属性名) */ + @NotBlank(message = "Java属性不能为空") + private String javaField; + + /** 是否主键(1-是,0-否) */ + private String isPk; + + /** 是否自增(1-是,0-否) */ + private String isIncrement; + + /** 是否必填(1-是,0-否) */ + private String isRequired; + + /** 是否为插入字段(1-是,0-否) */ + private String isInsert; + + /** 是否为编辑字段(1-是,0-否) */ + private String isEdit; + + /** 是否为列表字段(1-是,0-否) */ + private String isList; + + /** 是否为查询字段(1-是,0-否) */ + private String isQuery; + + /** 查询方式(EQ-等于、NE-不等于、GT-大于、LT-小于、LIKE-模糊、BETWEEN-范围) */ + private String queryType; + + /** 前端显示类型(input-文本框、textarea-文本域、select-下拉框等) */ + private String htmlType; + + /** 字典类型(关联字典表的类型,用于下拉选择等场景) */ + private String dictType; + + /** 排序号(用于字段展示顺序) */ + private Integer sort; + + // 以下为getter和setter方法 + + public void setColumnId(Long columnId) + { + this.columnId = columnId; + } + + public Long getColumnId() + { + return columnId; + } + + public void setTableId(Long tableId) + { + this.tableId = tableId; + } + + public Long getTableId() + { + return tableId; + } + + public void setColumnName(String columnName) + { + this.columnName = columnName; + } + + public String getColumnName() + { + return columnName; + } + + public void setColumnComment(String columnComment) + { + this.columnComment = columnComment; + } + + public String getColumnComment() + { + return columnComment; + } + + public void setColumnType(String columnType) + { + this.columnType = columnType; + } + + public String getColumnType() + { + return columnType; + } + + public void setJavaType(String javaType) + { + this.javaType = javaType; + } + + public String getJavaType() + { + return javaType; + } + + public void setJavaField(String javaField) + { + this.javaField = javaField; + } + + public String getJavaField() + { + return javaField; + } + + /** + * 获取首字母大写的Java字段名(用于生成getter/setter等方法) + * + * @return 首字母大写的Java字段名 + */ + public String getCapJavaField() + { + return StringUtils.capitalize(javaField); + } + + public void setIsPk(String isPk) + { + this.isPk = isPk; + } + + public String getIsPk() + { + return isPk; + } + + /** + * 判断当前字段是否为主键 + * + * @return true-是主键;false-不是 + */ + public boolean isPk() + { + return isPk(this.isPk); + } + + /** + * 静态方法:判断字段是否为主键 + * + * @param isPk 主键标识(1-是,0-否) + * @return true-是主键;false-不是 + */ + public boolean isPk(String isPk) + { + return isPk != null && StringUtils.equals("1", isPk); + } + + public String getIsIncrement() + { + return isIncrement; + } + + public void setIsIncrement(String isIncrement) + { + this.isIncrement = isIncrement; + } + + /** + * 判断当前字段是否为自增字段 + * + * @return true-是自增;false-不是 + */ + public boolean isIncrement() + { + return isIncrement(this.isIncrement); + } + + /** + * 静态方法:判断字段是否为自增字段 + * + * @param isIncrement 自增标识(1-是,0-否) + * @return true-是自增;false-不是 + */ + public boolean isIncrement(String isIncrement) + { + return isIncrement != null && StringUtils.equals("1", isIncrement); + } + + public void setIsRequired(String isRequired) + { + this.isRequired = isRequired; + } + + public String getIsRequired() + { + return isRequired; + } + + /** + * 判断当前字段是否为必填字段 + * + * @return true-是必填;false-不是 + */ + public boolean isRequired() + { + return isRequired(this.isRequired); + } + + /** + * 静态方法:判断字段是否为必填字段 + * + * @param isRequired 必填标识(1-是,0-否) + * @return true-是必填;false-不是 + */ + public boolean isRequired(String isRequired) + { + return isRequired != null && StringUtils.equals("1", isRequired); + } + + public void setIsInsert(String isInsert) + { + this.isInsert = isInsert; + } + + public String getIsInsert() + { + return isInsert; + } + + /** + * 判断当前字段是否为插入字段(新增时需要提交的字段) + * + * @return true-是插入字段;false-不是 + */ + public boolean isInsert() + { + return isInsert(this.isInsert); + } + + /** + * 静态方法:判断字段是否为插入字段 + * + * @param isInsert 插入标识(1-是,0-否) + * @return true-是插入字段;false-不是 + */ + public boolean isInsert(String isInsert) + { + return isInsert != null && StringUtils.equals("1", isInsert); + } + + public void setIsEdit(String isEdit) + { + this.isEdit = isEdit; + } + + public String getIsEdit() + { + return isEdit; + } + + /** + * 判断当前字段是否为编辑字段(修改时需要提交的字段) + * + * @return true-是编辑字段;false-不是 + */ + public boolean isEdit() + { + return isInsert(this.isEdit); + } + + /** + * 静态方法:判断字段是否为编辑字段 + * + * @param isEdit 编辑标识(1-是,0-否) + * @return true-是编辑字段;false-不是 + */ + public boolean isEdit(String isEdit) + { + return isEdit != null && StringUtils.equals("1", isEdit); + } + + public void setIsList(String isList) + { + this.isList = isList; + } + + public String getIsList() + { + return isList; + } + + /** + * 判断当前字段是否为列表字段(列表页需要显示的字段) + * + * @return true-是列表字段;false-不是 + */ + public boolean isList() + { + return isList(this.isList); + } + + /** + * 静态方法:判断字段是否为列表字段 + * + * @param isList 列表标识(1-是,0-否) + * @return true-是列表字段;false-不是 + */ + public boolean isList(String isList) + { + return isList != null && StringUtils.equals("1", isList); + } + + public void setIsQuery(String isQuery) + { + this.isQuery = isQuery; + } + + public String getIsQuery() + { + return isQuery; + } + + /** + * 判断当前字段是否为查询字段(查询条件中需要显示的字段) + * + * @return true-是查询字段;false-不是 + */ + public boolean isQuery() + { + return isQuery(this.isQuery); + } + + /** + * 静态方法:判断字段是否为查询字段 + * + * @param isQuery 查询标识(1-是,0-否) + * @return true-是查询字段;false-不是 + */ + public boolean isQuery(String isQuery) + { + return isQuery != null && StringUtils.equals("1", isQuery); + } + + public void setQueryType(String queryType) + { + this.queryType = queryType; + } + + public String getQueryType() + { + return queryType; + } + + public String getHtmlType() + { + return htmlType; + } + + public void setHtmlType(String htmlType) + { + this.htmlType = htmlType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + public String getDictType() + { + return dictType; + } + + public void setSort(Integer sort) + { + this.sort = sort; + } + + public Integer getSort() + { + return sort; + } + + /** + * 判断当前字段是否为超级字段(基类中已定义的字段,无需在生成的实体类中重复定义) + * + * @return true-是超级字段;false-不是 + */ + public boolean isSuperColumn() + { + return isSuperColumn(this.javaField); + } + + /** + * 静态方法:判断字段是否为超级字段 + * 超级字段包括BaseEntity和TreeEntity中已定义的公共字段 + * + * @param javaField Java字段名 + * @return true-是超级字段;false-不是 + */ + public static boolean isSuperColumn(String javaField) + { + return StringUtils.equalsAnyIgnoreCase(javaField, + // BaseEntity中的公共字段 + "createBy", "createTime", "updateBy", "updateTime", "remark", + // TreeEntity中的树相关字段 + "parentName", "parentId", "orderNum", "ancestors"); + } + + /** + * 判断当前字段是否为可用字段(虽为超级字段,但生成页面时需要用到,不能忽略) + * + * @return true-是可用字段;false-不是 + */ + public boolean isUsableColumn() + { + return isUsableColumn(javaField); + } + + /** + * 静态方法:判断字段是否为可用字段 + * 可用字段是超级字段中的白名单,生成页面时需要包含这些字段 + * + * @param javaField Java字段名 + * @return true-是可用字段;false-不是 + */ + public static boolean isUsableColumn(String javaField) + { + return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark"); + } + + /** + * 解析字段注释中的枚举说明,生成前端所需的转换表达式 + * 例如:注释为"状态(0-正常 1-禁用)",则生成"0=正常,1=禁用" + * + * @return 解析后的转换表达式 + */ + public String readConverterExp() + { + // 从字段注释中提取括号内的枚举说明(格式:(key-值 key-值...)) + String remarks = StringUtils.substringBetween(this.columnComment, "(", ")"); + StringBuffer sb = new StringBuffer(); + if (StringUtils.isNotEmpty(remarks)) + { + // 按空格拆分枚举项 + for (String value : remarks.split(" ")) + { + if (StringUtils.isNotEmpty(value)) + { + // 提取key和值(格式:key-值) + Object startStr = value.subSequence(0, 1); + String endStr = value.substring(1); + sb.append("").append(startStr).append("=").append(endStr).append(","); + } + } + // 移除最后一个逗号 + return sb.deleteCharAt(sb.length() - 1).toString(); + } + else + { + // 无枚举说明时返回原始注释 + return this.columnComment; + } + } +} \ No newline at end of file diff --git a/huacai-generator/src/main/java/com/huacai/generator/mapper/GenTableColumnMapper.java b/huacai-generator/src/main/java/com/huacai/generator/mapper/GenTableColumnMapper.java new file mode 100644 index 0000000..06e76a6 --- /dev/null +++ b/huacai-generator/src/main/java/com/huacai/generator/mapper/GenTableColumnMapper.java @@ -0,0 +1,60 @@ +package com.huacai.generator.mapper; + +import java.util.List; +import com.huacai.generator.domain.GenTableColumn; + +/** + * 业务字段 数据层 + * + * @author huacai + */ +public interface GenTableColumnMapper +{ + /** + * 根据表名称查询列信息 + * + * @param tableName 表名称 + * @return 列信息 + */ + public List selectDbTableColumnsByName(String tableName); + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenTableColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenTableColumn genTableColumn); + + /** + * 删除业务字段 + * + * @param genTableColumns 列数据 + * @return 结果 + */ + public int deleteGenTableColumns(List genTableColumns); + + /** + * 批量删除业务字段 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(Long[] ids); +} diff --git a/huacai-generator/src/main/java/com/huacai/generator/mapper/GenTableMapper.java b/huacai-generator/src/main/java/com/huacai/generator/mapper/GenTableMapper.java new file mode 100644 index 0000000..e3dbe6d --- /dev/null +++ b/huacai-generator/src/main/java/com/huacai/generator/mapper/GenTableMapper.java @@ -0,0 +1,83 @@ +package com.huacai.generator.mapper; + +import java.util.List; +import com.huacai.generator.domain.GenTable; + +/** + * 业务 数据层 + * + * @author huacai + */ +public interface GenTableMapper +{ + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询表ID业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 查询表名称业务信息 + * + * @param tableName 表名称 + * @return 业务信息 + */ + public GenTable selectGenTableByName(String tableName); + + /** + * 新增业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int insertGenTable(GenTable genTable); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int updateGenTable(GenTable genTable); + + /** + * 批量删除业务 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableByIds(Long[] ids); +} diff --git a/huacai-generator/src/main/java/com/huacai/generator/service/GenTableColumnServiceImpl.java b/huacai-generator/src/main/java/com/huacai/generator/service/GenTableColumnServiceImpl.java new file mode 100644 index 0000000..add060d --- /dev/null +++ b/huacai-generator/src/main/java/com/huacai/generator/service/GenTableColumnServiceImpl.java @@ -0,0 +1,75 @@ +package com.huacai.generator.service; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.huacai.common.core.text.Convert; +import com.huacai.generator.domain.GenTableColumn; +import com.huacai.generator.mapper.GenTableColumnMapper; + +/** + * 业务字段服务层实现类 + * 处理代码生成器中表字段的查询、新增、修改和删除等业务逻辑 + * + * @author huacai + */ +@Service +public class GenTableColumnServiceImpl implements IGenTableColumnService +{ + // 注入表字段Mapper接口,用于数据库操作 + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + /** + * 查询业务字段列表 + * 根据表ID查询该表对应的所有字段信息 + * + * @param tableId 表ID(归属表编号) + * @return 业务字段集合(表字段列表) + */ + @Override + public List selectGenTableColumnListByTableId(Long tableId) + { + return genTableColumnMapper.selectGenTableColumnListByTableId(tableId); + } + + /** + * 新增业务字段 + * 向数据库中插入一条表字段配置信息 + * + * @param genTableColumn 业务字段信息对象 + * @return 操作结果(影响的行数) + */ + @Override + public int insertGenTableColumn(GenTableColumn genTableColumn) + { + return genTableColumnMapper.insertGenTableColumn(genTableColumn); + } + + /** + * 修改业务字段 + * 根据字段ID更新表字段的配置信息 + * + * @param genTableColumn 业务字段信息对象(包含更新后的数据) + * @return 操作结果(影响的行数) + */ + @Override + public int updateGenTableColumn(GenTableColumn genTableColumn) + { + return genTableColumnMapper.updateGenTableColumn(genTableColumn); + } + + /** + * 批量删除业务字段 + * 根据ID字符串批量删除表字段配置信息 + * + * @param ids 需要删除的字段ID字符串(多个ID用逗号分隔) + * @return 操作结果(影响的行数) + */ + @Override + public int deleteGenTableColumnByIds(String ids) + { + // 将ID字符串转换为Long数组,调用Mapper进行批量删除 + return genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids)); + } +} diff --git a/huacai-generator/src/main/java/com/huacai/generator/service/GenTableServiceImpl.java b/huacai-generator/src/main/java/com/huacai/generator/service/GenTableServiceImpl.java new file mode 100644 index 0000000..ebe1dc6 --- /dev/null +++ b/huacai-generator/src/main/java/com/huacai/generator/service/GenTableServiceImpl.java @@ -0,0 +1,576 @@ +package com.huacai.generator.service; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.huacai.common.constant.Constants; +import com.huacai.common.constant.GenConstants; +import com.huacai.common.core.text.CharsetKit; +import com.huacai.common.exception.ServiceException; +import com.huacai.common.utils.SecurityUtils; +import com.huacai.common.utils.StringUtils; +import com.huacai.generator.domain.GenTable; +import com.huacai.generator.domain.GenTableColumn; +import com.huacai.generator.mapper.GenTableColumnMapper; +import com.huacai.generator.mapper.GenTableMapper; +import com.huacai.generator.util.GenUtils; +import com.huacai.generator.util.VelocityInitializer; +import com.huacai.generator.util.VelocityUtils; + +/** + * 代码生成业务服务层实现类 + * 处理代码生成过程中的表结构查询、导入、代码生成、预览、下载等核心业务逻辑 + * + * @author huacai + */ +@Service +public class GenTableServiceImpl implements IGenTableService +{ + // 日志记录器 + private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class); + + // 注入表结构Mapper接口,用于数据库操作 + @Autowired + private GenTableMapper genTableMapper; + + // 注入表字段Mapper接口,用于字段相关数据库操作 + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + /** + * 根据ID查询业务表信息 + * + * @param id 业务表ID + * @return 业务表信息对象(GenTable) + */ + @Override + public GenTable selectGenTableById(Long id) + { + GenTable genTable = genTableMapper.selectGenTableById(id); + // 从选项中解析并设置表的额外配置(如树表参数、菜单关联等) + setTableFromOptions(genTable); + return genTable; + } + + /** + * 查询业务表列表 + * + * @param genTable 业务表查询条件对象 + * @return 业务表集合 + */ + @Override + public List selectGenTableList(GenTable genTable) + { + return genTableMapper.selectGenTableList(genTable); + } + + /** + * 查询数据库中的表列表(未导入到代码生成器的表) + * + * @param genTable 业务表查询条件对象 + * @return 数据库表集合 + */ + @Override + public List selectDbTableList(GenTable genTable) + { + return genTableMapper.selectDbTableList(genTable); + } + + /** + * 根据表名数组查询数据库中的表信息 + * + * @param tableNames 表名数组 + * @return 数据库表集合 + */ + @Override + public List selectDbTableListByNames(String[] tableNames) + { + return genTableMapper.selectDbTableListByNames(tableNames); + } + + /** + * 查询所有已导入的业务表信息 + * + * @return 业务表集合 + */ + @Override + public List selectGenTableAll() + { + return genTableMapper.selectGenTableAll(); + } + + /** + * 修改业务表信息 + * 包含表配置和字段配置的更新 + * + * @param genTable 业务表信息对象(包含更新后的数据) + */ + @Override + @Transactional + public void updateGenTable(GenTable genTable) + { + // 将额外参数转换为JSON字符串存储 + String options = JSON.toJSONString(genTable.getParams()); + genTable.setOptions(options); + // 更新表信息 + int row = genTableMapper.updateGenTable(genTable); + if (row > 0) + { + // 批量更新字段信息 + for (GenTableColumn cenTableColumn : genTable.getColumns()) + { + genTableColumnMapper.updateGenTableColumn(cenTableColumn); + } + } + } + + /** + * 批量删除业务表 + * 同时删除关联的字段信息 + * + * @param tableIds 业务表ID数组 + */ + @Override + @Transactional + public void deleteGenTableByIds(Long[] tableIds) + { + // 删除表信息 + genTableMapper.deleteGenTableByIds(tableIds); + // 删除关联的字段信息 + genTableColumnMapper.deleteGenTableColumnByIds(tableIds); + } + + /** + * 导入表结构到代码生成器 + * 初始化表和字段信息并保存到数据库 + * + * @param tableList 待导入的表信息列表 + */ + @Override + @Transactional + public void importGenTable(List tableList) + { + String operName = SecurityUtils.getUsername(); + try + { + for (GenTable table : tableList) + { + String tableName = table.getTableName(); + // 初始化表信息(设置默认包路径、类名等) + GenUtils.initTable(table, operName); + // 插入表信息 + int row = genTableMapper.insertGenTable(table); + if (row > 0) + { + // 查询数据库中该表的字段信息 + List genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + // 初始化并插入字段信息 + for (GenTableColumn column : genTableColumns) + { + GenUtils.initColumnField(column, table); + genTableColumnMapper.insertGenTableColumn(column); + } + } + } + } + catch (Exception e) + { + throw new ServiceException("导入失败:" + e.getMessage()); + } + } + + /** + * 预览生成的代码 + * 渲染模板并返回各文件的代码内容 + * + * @param tableId 业务表ID + * @return 代码预览映射(key为模板路径,value为渲染后的代码) + */ + @Override + public Map previewCode(Long tableId) + { + Map dataMap = new LinkedHashMap<>(); + // 查询表信息 + GenTable table = genTableMapper.selectGenTableById(tableId); + // 设置主子表信息(如果是主子表模式) + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + // 初始化Velocity模板引擎 + VelocityInitializer.initVelocity(); + + // 准备模板上下文数据 + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表(根据表类型和前端类型选择) + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); + for (String template : templates) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + dataMap.put(template, sw.toString()); + } + return dataMap; + } + + /** + * 生成代码(下载方式) + * 将生成的代码打包为ZIP文件返回 + * + * @param tableName 表名 + * @return ZIP文件的字节数组 + */ + @Override + public byte[] downloadCode(String tableName) + { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + // 生成代码并写入ZIP流 + generatorCode(tableName, zip); + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 生成代码(自定义路径) + * 将生成的代码输出到指定路径 + * + * @param tableName 表名 + */ + @Override + public void generatorCode(String tableName) + { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + // 初始化Velocity模板引擎 + VelocityInitializer.initVelocity(); + + // 准备模板上下文数据 + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); + for (String template : templates) + { + // 过滤不需要生成到文件系统的模板(如SQL、前端特定文件) + if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm")) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try + { + // 获取生成路径并写入文件 + String path = getGenPath(table, template); + FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8); + } + catch (IOException e) + { + throw new ServiceException("渲染模板失败,表名:" + table.getTableName()); + } + } + } + } + + /** + * 同步数据库表结构 + * 将数据库中表结构的变更同步到代码生成器的配置中 + * + * @param tableName 表名 + */ + @Override + @Transactional + public void synchDb(String tableName) + { + // 查询当前表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + List tableColumns = table.getColumns(); + // 将现有字段按列名映射为Map,便于查询 + Map tableColumnMap = tableColumns.stream() + .collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity())); + + // 查询数据库中最新的表字段信息 + List dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + if (StringUtils.isEmpty(dbTableColumns)) + { + throw new ServiceException("同步数据失败,原表结构不存在"); + } + // 提取数据库字段名列表,用于判断字段是否被删除 + List dbTableColumnNames = dbTableColumns.stream() + .map(GenTableColumn::getColumnName) + .collect(Collectors.toList()); + + // 处理每个数据库字段:更新现有字段或新增字段 + dbTableColumns.forEach(column -> { + GenUtils.initColumnField(column, table); + if (tableColumnMap.containsKey(column.getColumnName())) + { + // 字段已存在,更新配置(保留部分原有配置) + GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName()); + column.setColumnId(prevColumn.getColumnId()); + if (column.isList()) + { + // 列表字段保留查询方式和字典类型 + column.setDictType(prevColumn.getDictType()); + column.setQueryType(prevColumn.getQueryType()); + } + if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk() + && (column.isInsert() || column.isEdit()) + && ((column.isUsableColumn()) || (!column.isSuperColumn()))) + { + // 保留必填和显示类型配置 + column.setIsRequired(prevColumn.getIsRequired()); + column.setHtmlType(prevColumn.getHtmlType()); + } + genTableColumnMapper.updateGenTableColumn(column); + } + else + { + // 字段不存在,新增字段配置 + genTableColumnMapper.insertGenTableColumn(column); + } + }); + + // 删除数据库中已不存在的字段配置 + List delColumns = tableColumns.stream() + .filter(column -> !dbTableColumnNames.contains(column.getColumnName())) + .collect(Collectors.toList()); + if (StringUtils.isNotEmpty(delColumns)) + { + genTableColumnMapper.deleteGenTableColumns(delColumns); + } + } + + /** + * 批量生成代码(下载方式) + * 将多个表的代码打包为ZIP文件返回 + * + * @param tableNames 表名数组 + * @return ZIP文件的字节数组 + */ + @Override + public byte[] downloadCode(String[] tableNames) + { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + for (String tableName : tableNames) + { + generatorCode(tableName, zip); + } + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 生成单个表的代码并写入ZIP流 + * + * @param tableName 表名 + * @param zip ZIP输出流 + */ + private void generatorCode(String tableName, ZipOutputStream zip) + { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + // 初始化Velocity模板引擎 + VelocityInitializer.initVelocity(); + + // 准备模板上下文数据 + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); + for (String template : templates) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try + { + // 添加到ZIP文件 + zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table))); + IOUtils.write(sw.toString(), zip, Constants.UTF8); + IOUtils.closeQuietly(sw); + zip.flush(); + zip.closeEntry(); + } + catch (IOException e) + { + log.error("渲染模板失败,表名:" + table.getTableName(), e); + } + } + } + + /** + * 验证编辑操作的参数合法性 + * 主要验证树表和主子表的必填配置 + * + * @param genTable 业务表信息对象 + */ + @Override + public void validateEdit(GenTable genTable) + { + if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) + { + // 树表模板需要验证树相关配置 + String options = JSON.toJSONString(genTable.getParams()); + JSONObject paramsObj = JSON.parseObject(options); + if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) + { + throw new ServiceException("树编码字段不能为空"); + } + else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) + { + throw new ServiceException("树父编码字段不能为空"); + } + else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) + { + throw new ServiceException("树名称字段不能为空"); + } + } + // 主子表模板需要验证子表配置 + if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) + { + if (StringUtils.isEmpty(genTable.getSubTableName())) + { + throw new ServiceException("关联子表的表名不能为空"); + } + else if (StringUtils.isEmpty(genTable.getSubTableFkName())) + { + throw new ServiceException("子表关联的外键名不能为空"); + } + } + } + + /** + * 设置主键列信息 + * 从字段列表中找出主键列并设置到表信息中 + * + * @param table 业务表信息 + */ + public void setPkColumn(GenTable table) + { + for (GenTableColumn column : table.getColumns()) + { + if (column.isPk()) + { + table.setPkColumn(column); + break; + } + } + // 若未找到主键,默认第一个字段为主键 + if (StringUtils.isNull(table.getPkColumn())) + { + table.setPkColumn(table.getColumns().get(0)); + } + // 处理子表的主键 + if (GenConstants.TPL_SUB.equals(table.getTplCategory())) + { + for (GenTableColumn column : table.getSubTable().getColumns()) + { + if (column.isPk()) + { + table.getSubTable().setPkColumn(column); + break; + } + } + if (StringUtils.isNull(table.getSubTable().getPkColumn())) + { + table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0)); + } + } + } + + /** + * 设置主子表信息 + * 若当前表是子表,查询并设置关联的主表信息 + * + * @param table 业务表信息 + */ + public void setSubTable(GenTable table) + { + String subTableName = table.getSubTableName(); + if (StringUtils.isNotEmpty(subTableName)) + { + table.setSubTable(genTableMapper.selectGenTableByName(subTableName)); + } + } + + /** + * 从选项中解析并设置表的额外配置 + * 如树表参数、菜单关联等信息 + * + * @param genTable 业务表信息 + */ + public void setTableFromOptions(GenTable genTable) + { + JSONObject paramsObj = JSON.parseObject(genTable.getOptions()); + if (StringUtils.isNotNull(paramsObj)) + { + String treeCode = paramsObj.getString(GenConstants.TREE_CODE); + String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID); + String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); + + genTable.setTreeCode(treeCode); + genTable.setTreeParentCode(treeParentCode); + genTable.setTreeName(treeName); + genTable.setParentMenuId(parentMenuId); + genTable.setParentMenuName(parentMenuName); + } + } + + /** + * 获取代码生成的目标路径 + * 根据配置的生成路径和模板计算最终文件路径 + * + * @param table 业务表信息 + * @param template 模板文件路径 + * @return 生成文件的完整路径 + */ + public static String getGenPath(GenTable table, String template) + { + String genPath = table.getGenPath(); + if (StringUtils.equals(genPath, "/")) + { + // 若生成路径为根目录,默认生成到项目src目录下 + return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table); + } + // 自定义路径下生成 + return genPath + File.separator + VelocityUtils.getFileName(template, table); + } +} diff --git a/huacai-generator/src/main/java/com/huacai/generator/service/IGenTableColumnService.java b/huacai-generator/src/main/java/com/huacai/generator/service/IGenTableColumnService.java new file mode 100644 index 0000000..129ac3c --- /dev/null +++ b/huacai-generator/src/main/java/com/huacai/generator/service/IGenTableColumnService.java @@ -0,0 +1,44 @@ +package com.huacai.generator.service; + +import java.util.List; +import com.huacai.generator.domain.GenTableColumn; + +/** + * 业务字段 服务层 + * + * @author huacai + */ +public interface IGenTableColumnService +{ + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenTableColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenTableColumn genTableColumn); + + /** + * 删除业务字段信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(String ids); +} diff --git a/huacai-generator/src/main/java/com/huacai/generator/service/IGenTableService.java b/huacai-generator/src/main/java/com/huacai/generator/service/IGenTableService.java new file mode 100644 index 0000000..6ea26ed --- /dev/null +++ b/huacai-generator/src/main/java/com/huacai/generator/service/IGenTableService.java @@ -0,0 +1,121 @@ +package com.huacai.generator.service; + +import java.util.List; +import java.util.Map; +import com.huacai.generator.domain.GenTable; + +/** + * 业务 服务层 + * + * @author huacai + */ +public interface IGenTableService +{ + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public void updateGenTable(GenTable genTable); + + /** + * 删除业务信息 + * + * @param tableIds 需要删除的表数据ID + * @return 结果 + */ + public void deleteGenTableByIds(Long[] tableIds); + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + */ + public void importGenTable(List tableList); + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + public Map previewCode(Long tableId); + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + public byte[] downloadCode(String tableName); + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + * @return 数据 + */ + public void generatorCode(String tableName); + + /** + * 同步数据库 + * + * @param tableName 表名称 + */ + public void synchDb(String tableName); + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + public byte[] downloadCode(String[] tableNames); + + /** + * 修改保存参数校验 + * + * @param genTable 业务信息 + */ + public void validateEdit(GenTable genTable); +} diff --git a/huacai-generator/src/main/java/com/huacai/generator/util/GenUtils.java b/huacai-generator/src/main/java/com/huacai/generator/util/GenUtils.java new file mode 100644 index 0000000..a4c26e2 --- /dev/null +++ b/huacai-generator/src/main/java/com/huacai/generator/util/GenUtils.java @@ -0,0 +1,302 @@ +package com.huacai.generator.util; + +import java.util.Arrays; +import org.apache.commons.lang3.RegExUtils; +import com.huacai.common.constant.GenConstants; +import com.huacai.common.utils.StringUtils; +import com.huacai.generator.config.GenConfig; +import com.huacai.generator.domain.GenTable; +import com.huacai.generator.domain.GenTableColumn; + +/** + * 代码生成器工具类 + * 提供表信息初始化、字段属性转换、名称格式处理等核心工具方法 + * + * @author huacai + */ +public class GenUtils +{ + /** + * 初始化表信息 + * 从配置和表名中自动填充类名、包路径、模块名等核心属性 + * + * @param genTable 待初始化的表信息对象 + * @param operName 操作人名称(用于创建人字段) + */ + public static void initTable(GenTable genTable, String operName) + { + // 从表名转换为实体类名(驼峰命名,首字母大写) + genTable.setClassName(convertClassName(genTable.getTableName())); + // 从配置中获取生成包路径 + genTable.setPackageName(GenConfig.getPackageName()); + // 从包路径中提取模块名(如包路径com.huacai.modules.system,模块名为system) + genTable.setModuleName(getModuleName(GenConfig.getPackageName())); + // 从表名中提取业务名(如表名sys_user,业务名为user) + genTable.setBusinessName(getBusinessName(genTable.getTableName())); + // 从表描述中提取功能名(去除"表"等冗余字符) + genTable.setFunctionName(replaceText(genTable.getTableComment())); + // 从配置中获取作者信息 + genTable.setFunctionAuthor(GenConfig.getAuthor()); + // 设置创建人 + genTable.setCreateBy(operName); + } + + /** + * 初始化列属性字段 + * 根据数据库字段类型自动映射Java类型、前端显示类型等属性 + * + * @param column 待初始化的字段信息对象 + * @param table 字段所属的表信息对象 + */ + public static void initColumnField(GenTableColumn column, GenTable table) + { + // 提取数据库字段类型(去除长度信息,如varchar(50)→varchar) + String dataType = getDbType(column.getColumnType()); + String columnName = column.getColumnName(); + + // 设置字段关联的表ID和创建人 + column.setTableId(table.getTableId()); + column.setCreateBy(table.getCreateBy()); + + // 数据库字段名转换为Java属性名(下划线转驼峰,首字母小写) + column.setJavaField(StringUtils.toCamelCase(columnName)); + // 设置Java类型默认值(默认String) + column.setJavaType(GenConstants.TYPE_STRING); + // 设置查询方式默认值(默认等于查询) + column.setQueryType(GenConstants.QUERY_EQ); + + // 处理字符串/文本类型字段 + if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) + { + // 获取字段长度(如varchar(50)→50) + Integer columnLength = getColumnLength(column.getColumnType()); + // 长度≥500或文本类型(text)用文本域,否则用输入框 + String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) + ? GenConstants.HTML_TEXTAREA + : GenConstants.HTML_INPUT; + column.setHtmlType(htmlType); + } + // 处理时间类型字段 + else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) + { + column.setJavaType(GenConstants.TYPE_DATE); // 映射为Date类型 + column.setHtmlType(GenConstants.HTML_DATETIME); // 前端用日期时间控件 + } + // 处理整数类型字段(int、tinyint等) + else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER_INT, dataType)) + { + column.setHtmlType(GenConstants.HTML_INPUT); // 前端用输入框 + column.setJavaType(GenConstants.TYPE_INTEGER); // 映射为Integer类型 + } + // 处理长整数类型字段(bigint) + else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER_LONG, dataType)) + { + column.setHtmlType(GenConstants.HTML_INPUT); // 前端用输入框 + column.setJavaType(GenConstants.TYPE_LONG); // 映射为Long类型 + } + // 处理浮点数类型字段(decimal、double等) + else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER_REAL, dataType)) + { + column.setHtmlType(GenConstants.HTML_INPUT); // 前端用输入框 + column.setJavaType(GenConstants.TYPE_BIGDECIMAL); // 映射为BigDecimal类型 + } + // 处理布尔类型字段(bit) + else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER_BIT, dataType)) + { + column.setHtmlType(GenConstants.HTML_RADIO); // 前端用单选框 + column.setJavaType(GenConstants.TYPE_BIGDECIMAL); // 映射为BigDecimal类型 + } + + // 插入字段配置:默认所有字段都允许插入 + column.setIsInsert(GenConstants.REQUIRE); + + // 编辑字段配置:排除主键和不允许编辑的字段(如create_time) + if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk()) + { + column.setIsEdit(GenConstants.REQUIRE); + } + // 列表字段配置:排除主键和不显示在列表的字段(如content) + if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk()) + { + column.setIsList(GenConstants.REQUIRE); + } + // 查询字段配置:排除主键和不用于查询的字段(如remark) + if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) + { + column.setIsQuery(GenConstants.REQUIRE); + } + + // 特殊查询方式配置:以"name"结尾的字段(如username)默认用模糊查询 + if (StringUtils.endsWithIgnoreCase(columnName, "name")) + { + column.setQueryType(GenConstants.QUERY_LIKE); + } + // 特殊前端控件配置:"status"结尾的字段(如status)默认用单选框 + if (StringUtils.endsWithIgnoreCase(columnName, "status")) + { + column.setHtmlType(GenConstants.HTML_RADIO); + } + // 特殊前端控件配置:"type"或"sex"结尾的字段默认用下拉框 + else if (StringUtils.endsWithIgnoreCase(columnName, "type") + || StringUtils.endsWithIgnoreCase(columnName, "sex")) + { + column.setHtmlType(GenConstants.HTML_SELECT); + } + // 特殊前端控件配置:"image"结尾的字段默认用图片上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "image")) + { + column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD); + } + // 特殊前端控件配置:"file"结尾的字段默认用文件上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "file")) + { + column.setHtmlType(GenConstants.HTML_FILE_UPLOAD); + } + // 特殊前端控件配置:"content"结尾的字段默认用富文本控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "content")) + { + column.setHtmlType(GenConstants.HTML_EDITOR); + } + } + + /** + * 校验数组是否包含指定值 + * 用于判断字段类型是否属于目标类型集合(如判断是否为字符串类型) + * + * @param arr 目标数组 + * @param targetValue 待校验的值 + * @return true-包含;false-不包含 + */ + public static boolean arraysContains(String[] arr, String targetValue) + { + return Arrays.asList(arr).contains(targetValue); + } + + /** + * 从包路径中提取模块名 + * 规则:取包路径最后一个"."后的内容(如com.huacai.modules.system→system) + * + * @param packageName 完整包路径 + * @return 模块名 + */ + public static String getModuleName(String packageName) + { + int lastIndex = packageName.lastIndexOf("."); + int nameLength = packageName.length(); + return StringUtils.substring(packageName, lastIndex + 1, nameLength); + } + + /** + * 从表名中提取业务名 + * 规则:去除表前缀后,取剩余部分(如sys_user→user,若无前缀则直接用表名) + * + * @param tableName 数据库表名 + * @return 业务名 + */ + public static String getBusinessName(String tableName) + { + int lastIndex = tableName.lastIndexOf("_"); + int nameLength = tableName.length(); + return StringUtils.substring(tableName, lastIndex + 1, nameLength); + } + + /** + * 表名转换为Java实体类名 + * 规则:先去除表前缀(若配置自动去除),再转驼峰命名并首字母大写 + * + * @param tableName 数据库表名 + * @return Java实体类名 + */ + public static String convertClassName(String tableName) + { + // 获取是否自动去除表前缀的配置 + boolean autoRemovePre = GenConfig.getAutoRemovePre(); + // 获取配置的表前缀(多个前缀用逗号分隔) + String tablePrefix = GenConfig.getTablePrefix(); + + // 若开启自动去除前缀且前缀不为空,先去除表前缀 + if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) + { + String[] searchList = StringUtils.split(tablePrefix, ","); + tableName = replaceFirst(tableName, searchList); + } + + // 表名转驼峰命名(首字母大写) + return StringUtils.convertToCamelCase(tableName); + } + + /** + * 批量替换表名前缀 + * 从前缀列表中匹配第一个符合的前缀并去除 + * + * @param replacementm 原始表名 + * @param searchList 前缀列表 + * @return 去除前缀后的表名 + */ + public static String replaceFirst(String replacementm, String[] searchList) + { + String text = replacementm; + for (String searchString : searchList) + { + // 若表名以当前前缀开头,去除该前缀并跳出循环 + if (replacementm.startsWith(searchString)) + { + text = replacementm.replaceFirst(searchString, ""); + break; + } + } + return text; + } + + /** + * 关键字替换(清理表描述) + * 去除表描述中的"表"、"若依"等冗余字符,提取纯净功能名 + * + * @param text 原始表描述 + * @return 清理后的功能名 + */ + public static String replaceText(String text) + { + return RegExUtils.replaceAll(text, "(?:表|若依)", ""); + } + + /** + * 提取数据库字段类型(去除长度信息) + * 如"varchar(50)"→"varchar","int"→"int" + * + * @param columnType 原始字段类型(含长度) + * @return 纯净字段类型 + */ + public static String getDbType(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + return StringUtils.substringBefore(columnType, "("); + } + else + { + return columnType; + } + } + + /** + * 提取数据库字段长度 + * 如"varchar(50)"→50,无长度信息则返回0 + * + * @param columnType 原始字段类型(含长度) + * @return 字段长度(整数) + */ + public static Integer getColumnLength(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + // 提取括号中的长度数值 + String length = StringUtils.substringBetween(columnType, "(", ")"); + return Integer.valueOf(length); + } + else + { + return 0; + } + } +} \ No newline at end of file diff --git a/huacai-generator/src/main/java/com/huacai/generator/util/VelocityInitializer.java b/huacai-generator/src/main/java/com/huacai/generator/util/VelocityInitializer.java new file mode 100644 index 0000000..1eb174d --- /dev/null +++ b/huacai-generator/src/main/java/com/huacai/generator/util/VelocityInitializer.java @@ -0,0 +1,34 @@ +package com.huacai.generator.util; + +import java.util.Properties; +import org.apache.velocity.app.Velocity; +import com.huacai.common.constant.Constants; + +/** + * VelocityEngine工厂 + * + * @author huacai + */ +public class VelocityInitializer +{ + /** + * 初始化vm方法 + */ + public static void initVelocity() + { + Properties p = new Properties(); + try + { + // 加载classpath目录下的vm文件 + p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + // 定义字符集 + p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8); + // 初始化Velocity引擎,指定配置Properties + Velocity.init(p); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/huacai-generator/src/main/java/com/huacai/generator/util/VelocityUtils.java b/huacai-generator/src/main/java/com/huacai/generator/util/VelocityUtils.java new file mode 100644 index 0000000..6a0818c --- /dev/null +++ b/huacai-generator/src/main/java/com/huacai/generator/util/VelocityUtils.java @@ -0,0 +1,477 @@ +package com.huacai.generator.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.velocity.VelocityContext; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.huacai.common.constant.GenConstants; +import com.huacai.common.utils.DateUtils; +import com.huacai.common.utils.StringUtils; +import com.huacai.generator.domain.GenTable; +import com.huacai.generator.domain.GenTableColumn; + +/** + * 模板处理工具类 + * 负责Velocity模板引擎的上下文准备、模板文件选择、生成文件名计算等核心功能 + * + * @author huacai + */ +public class VelocityUtils +{ + /** 项目Java代码空间路径(Maven标准目录结构) */ + private static final String PROJECT_PATH = "main/java"; + + /** MyBatis映射文件空间路径(Maven标准目录结构) */ + private static final String MYBATIS_PATH = "main/resources/mapper"; + + /** 默认上级菜单ID(系统工具菜单) */ + private static final String DEFAULT_PARENT_MENU_ID = "3"; + + /** + * 准备Velocity模板上下文数据 + * 将表信息、字段信息等转换为模板所需的变量,供模板渲染使用 + * + * @param genTable 业务表信息对象 + * @return 填充好数据的Velocity上下文对象 + */ + public static VelocityContext prepareContext(GenTable genTable) + { + String moduleName = genTable.getModuleName(); + String businessName = genTable.getBusinessName(); + String packageName = genTable.getPackageName(); + String tplCategory = genTable.getTplCategory(); + String functionName = genTable.getFunctionName(); + + VelocityContext velocityContext = new VelocityContext(); + + // 基础变量:模板类型、表名、功能名等 + velocityContext.put("tplCategory", genTable.getTplCategory()); + velocityContext.put("tableName", genTable.getTableName()); + velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】"); + velocityContext.put("ClassName", genTable.getClassName()); // 实体类名(首字母大写) + velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName())); // 实体类名(首字母小写) + velocityContext.put("moduleName", moduleName); + velocityContext.put("BusinessName", StringUtils.capitalize(businessName)); // 业务名(首字母大写) + velocityContext.put("businessName", businessName); // 业务名(首字母小写) + velocityContext.put("basePackage", getPackagePrefix(packageName)); // 基础包路径(去除最后一级) + velocityContext.put("packageName", packageName); // 完整包路径 + velocityContext.put("author", genTable.getFunctionAuthor()); // 作者 + velocityContext.put("datetime", DateUtils.getDate()); // 当前日期 + velocityContext.put("pkColumn", genTable.getPkColumn()); // 主键字段 + velocityContext.put("importList", getImportList(genTable)); // 需要导入的Java类 + velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); // 权限前缀 + velocityContext.put("columns", genTable.getColumns()); // 所有字段列表 + velocityContext.put("table", genTable); // 表信息对象 + velocityContext.put("dicts", getDicts(genTable)); // 关联的字典类型列表 + + // 设置菜单相关变量 + setMenuVelocityContext(velocityContext, genTable); + + // 树表模板特有变量 + if (GenConstants.TPL_TREE.equals(tplCategory)) + { + setTreeVelocityContext(velocityContext, genTable); + } + + // 主子表模板特有变量 + if (GenConstants.TPL_SUB.equals(tplCategory)) + { + setSubVelocityContext(velocityContext, genTable); + } + + return velocityContext; + } + + /** + * 设置菜单相关的模板变量 + * 主要处理上级菜单ID + * + * @param context Velocity上下文 + * @param genTable 业务表信息 + */ + public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + // 获取上级菜单ID(配置优先,无配置则用默认值) + String parentMenuId = getParentMenuId(paramsObj); + context.put("parentMenuId", parentMenuId); + } + + /** + * 设置树表模板特有变量 + * 包括树编码、父编码、树名称等树结构相关字段 + * + * @param context Velocity上下文 + * @param genTable 业务表信息 + */ + public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + + // 树表核心字段(从配置中提取) + String treeCode = getTreecode(paramsObj); + String treeParentCode = getTreeParentCode(paramsObj); + String treeName = getTreeName(paramsObj); + + context.put("treeCode", treeCode); + context.put("treeParentCode", treeParentCode); + context.put("treeName", treeName); + context.put("expandColumn", getExpandColumn(genTable)); // 树节点展开按钮所在列 + + // 额外的树表参数 + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) + { + context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + if (paramsObj.containsKey(GenConstants.TREE_NAME)) + { + context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME)); + } + } + + /** + * 设置主子表模板特有变量 + * 包括子表信息、外键关联等字段 + * + * @param context Velocity上下文 + * @param genTable 业务表信息(主表) + */ + public static void setSubVelocityContext(VelocityContext context, GenTable genTable) + { + GenTable subTable = genTable.getSubTable(); + String subTableName = genTable.getSubTableName(); + String subTableFkName = genTable.getSubTableFkName(); + String subClassName = subTable.getClassName(); + String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName); // 外键字段名(驼峰) + + context.put("subTable", subTable); // 子表信息对象 + context.put("subTableName", subTableName); // 子表名 + context.put("subTableFkName", subTableFkName); // 外键名 + context.put("subTableFkClassName", subTableFkClassName); // 外键Java属性名(首字母大写) + context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName)); // 外键Java属性名(首字母小写) + context.put("subClassName", subClassName); // 子表实体类名(首字母大写) + context.put("subclassName", StringUtils.uncapitalize(subClassName)); // 子表实体类名(首字母小写) + context.put("subImportList", getImportList(subTable)); // 子表需要导入的Java类 + } + + /** + * 获取需要渲染的模板列表 + * 根据模板类型(单表/树表/主子表)和前端类型(element-ui/element-plus)选择对应模板 + * + * @param tplCategory 模板类型(CRUD/TREE/SUB) + * @param tplWebType 前端框架类型 + * @return 模板文件路径列表 + */ + public static List getTemplateList(String tplCategory, String tplWebType) + { + // 根据前端类型选择模板目录(默认vue2,element-plus对应vue3) + String useWebType = "vm/vue"; + if ("element-plus".equals(tplWebType)) + { + useWebType = "vm/vue/v3"; + } + + List templates = new ArrayList(); + // 通用模板(所有类型都需要生成) + templates.add("vm/java/domain.java.vm"); // 实体类 + templates.add("vm/java/mapper.java.vm"); // Mapper接口 + templates.add("vm/java/service.java.vm"); // Service接口 + templates.add("vm/java/serviceImpl.java.vm"); // Service实现类 + templates.add("vm/java/controller.java.vm"); // Controller类 + templates.add("vm/xml/mapper.xml.vm"); // MyBatis映射文件 + templates.add("vm/sql/sql.vm"); // 菜单SQL脚本 + templates.add("vm/js/api.js.vm"); // 前端API请求 + + // 根据模板类型添加特有模板 + if (GenConstants.TPL_CRUD.equals(tplCategory)) + { + templates.add(useWebType + "/index.vue.vm"); // 单表列表页 + } + else if (GenConstants.TPL_TREE.equals(tplCategory)) + { + templates.add(useWebType + "/index-tree.vue.vm"); // 树表列表页 + } + else if (GenConstants.TPL_SUB.equals(tplCategory)) + { + templates.add(useWebType + "/index.vue.vm"); // 主子表列表页 + templates.add("vm/java/sub-domain.java.vm"); // 子表实体类 + } + return templates; + } + + /** + * 计算生成文件的完整路径和名称 + * 根据模板类型和表信息生成对应的文件路径(如实体类放在domain包下) + * + * @param template 模板文件路径 + * @param genTable 业务表信息 + * @return 生成文件的完整路径和名称 + */ + public static String getFileName(String template, GenTable genTable) + { + String fileName = ""; + String packageName = genTable.getPackageName(); + String moduleName = genTable.getModuleName(); + String className = genTable.getClassName(); + String businessName = genTable.getBusinessName(); + + // 基础路径计算 + String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/"); // Java代码根路径(包路径转目录) + String mybatisPath = MYBATIS_PATH + "/" + moduleName; // MyBatis映射文件路径 + String vuePath = "vue"; // 前端代码根路径 + + // 根据模板类型计算具体文件名 + if (template.contains("domain.java.vm")) + { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, className); + } + // 主子表的子表实体类 + else if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory())) + { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName()); + } + else if (template.contains("mapper.java.vm")) + { + fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className); + } + else if (template.contains("service.java.vm")) + { + fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className); + } + else if (template.contains("serviceImpl.java.vm")) + { + fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className); + } + else if (template.contains("controller.java.vm")) + { + fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className); + } + else if (template.contains("mapper.xml.vm")) + { + fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className); + } + else if (template.contains("sql.vm")) + { + fileName = businessName + "Menu.sql"; + } + else if (template.contains("api.js.vm")) + { + fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName); + } + else if (template.contains("index.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + else if (template.contains("index-tree.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + return fileName; + } + + /** + * 获取基础包路径(去除包名最后一级) + * 如com.huacai.system.user→com.huacai.system + * + * @param packageName 完整包名 + * @return 基础包路径 + */ + public static String getPackagePrefix(String packageName) + { + int lastIndex = packageName.lastIndexOf("."); + return StringUtils.substring(packageName, 0, lastIndex); + } + + /** + * 计算需要导入的Java类列表 + * 根据字段类型自动添加必要的导入(如Date、BigDecimal等) + * + * @param genTable 业务表信息 + * @return 导入类的全限定名集合(去重) + */ + public static HashSet getImportList(GenTable genTable) + { + List columns = genTable.getColumns(); + GenTable subGenTable = genTable.getSubTable(); + HashSet importList = new HashSet(); + + // 主子表需要导入List + if (StringUtils.isNotNull(subGenTable)) + { + importList.add("java.util.List"); + } + + // 根据字段类型添加导入 + for (GenTableColumn column : columns) + { + // 非超级字段且类型为Date时,导入Date和JsonFormat注解 + if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) + { + importList.add("java.util.Date"); + importList.add("com.fasterxml.jackson.annotation.JsonFormat"); + } + // 非超级字段且类型为BigDecimal时,导入BigDecimal + else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) + { + importList.add("java.math.BigDecimal"); + } + } + return importList; + } + + /** + * 获取关联的字典类型列表 + * 用于前端渲染字典下拉框时导入所需字典 + * + * @param genTable 业务表信息 + * @return 字典类型字符串(用逗号分隔) + */ + public static String getDicts(GenTable genTable) + { + List columns = genTable.getColumns(); + Set dicts = new HashSet(); + addDicts(dicts, columns); + + // 主子表需要包含子表的字典 + if (StringUtils.isNotNull(genTable.getSubTable())) + { + List subColumns = genTable.getSubTable().getColumns(); + addDicts(dicts, subColumns); + } + return StringUtils.join(dicts, ", "); + } + + /** + * 向字典集合中添加字段关联的字典类型 + * 仅处理下拉框、单选框、复选框类型的字段 + * + * @param dicts 字典集合 + * @param columns 字段列表 + */ + public static void addDicts(Set dicts, List columns) + { + for (GenTableColumn column : columns) + { + // 非超级字段、有字典类型、且前端控件为选择类时添加 + if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny( + column.getHtmlType(), + new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) + { + dicts.add("'" + column.getDictType() + "'"); + } + } + } + + /** + * 生成权限前缀 + * 格式:模块名:业务名(用于权限控制) + * + * @param moduleName 模块名 + * @param businessName 业务名 + * @return 权限前缀字符串 + */ + public static String getPermissionPrefix(String moduleName, String businessName) + { + return StringUtils.format("{}:{}", moduleName, businessName); + } + + /** + * 获取上级菜单ID + * 从配置中提取,无配置则使用默认值 + * + * @param paramsObj 配置参数对象 + * @return 上级菜单ID + */ + public static String getParentMenuId(JSONObject paramsObj) + { + if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID) + && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID))) + { + return paramsObj.getString(GenConstants.PARENT_MENU_ID); + } + return DEFAULT_PARENT_MENU_ID; + } + + /** + * 获取树编码字段(Java属性名) + * 从配置中提取并转换为驼峰命名 + * + * @param paramsObj 配置参数对象 + * @return 树编码字段名(驼峰) + */ + public static String getTreecode(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_CODE)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树父编码字段(Java属性名) + * 从配置中提取并转换为驼峰命名 + * + * @param paramsObj 配置参数对象 + * @return 树父编码字段名(驼峰) + */ + public static String getTreeParentCode(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树名称字段(Java属性名) + * 从配置中提取并转换为驼峰命名 + * + * @param paramsObj 配置参数对象 + * @return 树名称字段名(驼峰) + */ + public static String getTreeName(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_NAME)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME)); + } + return StringUtils.EMPTY; + } + + /** + * 计算树表展开按钮所在列的序号 + * 用于前端树表渲染时确定在哪一列显示展开/折叠按钮 + * + * @param genTable 业务表信息 + * @return 展开按钮列的序号(从1开始) + */ + public static int getExpandColumn(GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + int num = 0; + + // 遍历列表字段,找到树名称字段所在的列序号 + for (GenTableColumn column : genTable.getColumns()) + { + if (column.isList()) + { + num++; + String columnName = column.getColumnName(); + if (columnName.equals(treeName)) + { + break; + } + } + } + return num; + } +} \ No newline at end of file diff --git a/huacai-generator/src/main/resources/generator.yml b/huacai-generator/src/main/resources/generator.yml new file mode 100644 index 0000000..5eb98d3 --- /dev/null +++ b/huacai-generator/src/main/resources/generator.yml @@ -0,0 +1,10 @@ +# 代码生成 +gen: + # 作者 + author: huacai + # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool + packageName: com.huacai + # 自动去除表前缀,默认是false + autoRemovePre: false + # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) + tablePrefix: ylxt_ diff --git a/huacai-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/huacai-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml new file mode 100644 index 0000000..1e3746b --- /dev/null +++ b/huacai-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select + column_id, table_id, column_name, column_comment, column_type, + java_type, java_field, is_pk, is_increment, is_required, + is_insert, is_edit, is_list, is_query, query_type, html_type, + dict_type, sort, create_by, create_time, update_by, update_time + from gen_table_column + + + + + + + + + + + insert into gen_table_column ( + table_id, + column_name, + column_comment, + column_type, + java_type, + java_field, + is_pk, + is_increment, + is_required, + is_insert, + is_edit, + is_list, + is_query, + query_type, + html_type, + dict_type, + sort, + create_by, + create_time + )values( + #{tableId}, + #{columnName}, + #{columnComment}, + #{columnType}, + #{javaType}, + #{javaField}, + #{isPk}, + #{isIncrement}, + #{isRequired}, + #{isInsert}, + #{isEdit}, + #{isList}, + #{isQuery}, + #{queryType}, + #{htmlType}, + #{dictType}, + #{sort}, + #{createBy}, + sysdate() -- 创建时间为当前系统时间 + ) + + + + + update gen_table_column + + column_comment = #{columnComment}, + java_type = #{javaType}, + java_field = #{javaField}, + is_insert = #{isInsert}, + is_edit = #{isEdit}, + is_list = #{isList}, + is_query = #{isQuery}, + is_required = #{isRequired}, + query_type = #{queryType}, + html_type = #{htmlType}, + dict_type = #{dictType}, + sort = #{sort}, + update_by = #{updateBy}, + update_time = sysdate() -- 更新时间为当前系统时间 + + where column_id = #{columnId} -- 按主键更新 + + + + + delete from gen_table_column where table_id in + + #{tableId} + + + + + + delete from gen_table_column where column_id in + + #{item.columnId} + + + + \ No newline at end of file diff --git a/huacai-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/huacai-generator/src/main/resources/mapper/generator/GenTableMapper.xml new file mode 100644 index 0000000..f5b0233 --- /dev/null +++ b/huacai-generator/src/main/resources/mapper/generator/GenTableMapper.xml @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select + table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, + class_name, tpl_category, tpl_web_type, package_name, module_name, + business_name, function_name, function_author, gen_type, gen_path, + options, create_by, create_time, update_by, update_time, remark + from gen_table + + + + + + + + + + + + + + + + + + + + + + + + + + insert into gen_table ( + table_name, + table_comment, + class_name, + tpl_category, + tpl_web_type, + package_name, + module_name, + business_name, + function_name, + function_author, + gen_type, + gen_path, + remark, + create_by, + create_time + )values( + #{tableName}, + #{tableComment}, + #{className}, + #{tplCategory}, + #{tplWebType}, + #{packageName}, + #{moduleName}, + #{businessName}, + #{functionName}, + #{functionAuthor}, + #{genType}, + #{genPath}, + #{remark}, + #{createBy}, + sysdate() -- 创建时间为当前系统时间 + ) + + + + + update gen_table + + table_name = #{tableName}, + table_comment = #{tableComment}, + sub_table_name = #{subTableName}, + sub_table_fk_name = #{subTableFkName}, + class_name = #{className}, + function_author = #{functionAuthor}, + gen_type = #{genType}, + gen_path = #{genPath}, + tpl_category = #{tplCategory}, + tpl_web_type = #{tplWebType}, + package_name = #{packageName}, + module_name = #{moduleName}, + business_name = #{businessName}, + function_name = #{functionName}, + options = #{options}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() -- 更新时间为当前系统时间 + + where table_id = #{tableId} -- 按主键更新 + + + + + delete from gen_table where table_id in + + #{tableId} + + + + \ No newline at end of file diff --git a/huacai-generator/src/main/resources/vm/java/controller.java.vm b/huacai-generator/src/main/resources/vm/java/controller.java.vm new file mode 100644 index 0000000..6617abd --- /dev/null +++ b/huacai-generator/src/main/resources/vm/java/controller.java.vm @@ -0,0 +1,162 @@ +package ${packageName}.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.huacai.common.annotation.Log; +import com.huacai.common.core.controller.BaseController; +import com.huacai.common.core.domain.AjaxResult; +import com.huacai.common.enums.BusinessType; +import java.io.InputStream; +import org.springframework.web.multipart.MultipartFile; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import com.huacai.common.utils.poi.ExcelUtil; +#if($table.crud || $table.sub) +import com.huacai.common.core.page.TableDataInfo; +#elseif($table.tree) +#end + +/** + * ${functionName}前端控制器 + * 提供${functionName}的CRUD、导入导出等接口服务 + * + * @author ${author} + * @date ${datetime} + */ +@RestController +@RequestMapping("/${moduleName}/${businessName}") +public class ${ClassName}Controller extends BaseController +{ + @Autowired + private I${ClassName}Service ${className}Service; + +/** + * 查询${functionName}列表 + * 支持分页查询和条件过滤 + */ +@PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')") // 权限控制:需要${permissionPrefix}:list权限 +@GetMapping("/list") + #if($table.crud || $table.sub) + public TableDataInfo list(${ClassName} ${className}) + { + startPage(); // 开启分页(BaseController提供的分页工具) + // 调用Service层查询列表 + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return getDataTable(list); // 包装成分页响应对象 + } + #elseif($table.tree) + public AjaxResult list(${ClassName} ${className}) + { + // 树结构数据不需要分页,直接返回列表 + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return success(list); // 包装成成功响应 + } + #end + + /** + * 导出${functionName}列表 + * 将查询结果导出为Excel文件 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')") // 权限控制:需要${permissionPrefix}:export权限 + @Log(title = "${functionName}", businessType = BusinessType.EXPORT) // 操作日志记录 + @PostMapping("/export") + public void export(HttpServletResponse response, ${ClassName} ${className}) + { + // 查询待导出的数据列表 + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + // 使用Excel工具类导出 + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + util.exportExcel(response, list, "${functionName}数据"); + } + + /** + * 下载导入模板 + * 提供标准的Excel导入模板供用户填写数据 + */ + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) + { + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + util.importTemplateExcel(response, "${functionName}数据"); + } + + /** + * 导入数据 + * 从Excel文件导入数据并批量保存 + */ + @Log(title = "${functionName}", businessType = BusinessType.IMPORT) // 操作日志记录 + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:import')") // 权限控制:需要${permissionPrefix}:import权限 + @PostMapping("/importData") + public AjaxResult importData(MultipartFile file) throws Exception + { + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + InputStream inputStream = file.getInputStream(); + // 解析Excel文件得到数据列表 + List<${ClassName}> list = util.importExcel(inputStream ); + inputStream.close(); + // 调用Service批量插入 + int count = ${className}Service.batchInsert${ClassName}(list); + return AjaxResult.success("导入成功" + count + "条信息!"); + } + + /** + * 获取${functionName}详细信息 + * 根据主键查询单条记录详情 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:query')") // 权限控制:需要${permissionPrefix}:query权限 + @GetMapping(value = "/{${pkColumn.javaField}}") + public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) + { + // 调用Service查询详情 + return success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); + } + + /** + * 新增${functionName} + * 新增一条记录 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:add')") // 权限控制:需要${permissionPrefix}:add权限 + @Log(title = "${functionName}", businessType = BusinessType.INSERT) // 操作日志记录 + @PostMapping + public AjaxResult add(@RequestBody ${ClassName} ${className}) + { + // 调用Service插入数据,返回影响行数 + return toAjax(${className}Service.insert${ClassName}(${className})); + } + + /** + * 修改${functionName} + * 更新一条记录 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:edit')") // 权限控制:需要${permissionPrefix}:edit权限 + @Log(title = "${functionName}", businessType = BusinessType.UPDATE) // 操作日志记录 + @PutMapping + public AjaxResult edit(@RequestBody ${ClassName} ${className}) + { + // 调用Service更新数据,返回影响行数 + return toAjax(${className}Service.update${ClassName}(${className})); + } + + /** + * 删除${functionName} + * 批量删除记录 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:remove')") // 权限控制:需要${permissionPrefix}:remove权限 + @Log(title = "${functionName}", businessType = BusinessType.DELETE) // 操作日志记录 + @DeleteMapping("/{${pkColumn.javaField}s}") + public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) + { + // 调用Service批量删除,返回影响行数 + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s)); + } +} \ No newline at end of file diff --git a/huacai-generator/src/main/resources/vm/java/domain.java.vm b/huacai-generator/src/main/resources/vm/java/domain.java.vm new file mode 100644 index 0000000..75a016c --- /dev/null +++ b/huacai-generator/src/main/resources/vm/java/domain.java.vm @@ -0,0 +1,59 @@ +package ${packageName}.domain; + + #foreach ($import in $importList) + import ${import}; + #end +import com.huacai.common.annotation.Excel; +import lombok.*; +import com.huacai.common.core.domain.BaseEntity; + +/** + * ${functionName}对象 ${tableName} + * 对应数据库表:${tableName},用于封装${functionName}相关数据 + * + * @author ${author} + * @date ${datetime} + */ + #if($table.crud || $table.sub) + #set($Entity="BaseEntity") // 单表/主子表继承基础实体类(含创建时间、更新时间等通用字段) + #elseif($table.tree) + #set($Entity="TreeEntity") // 树表继承树形实体类(含父ID、排序等树形结构字段) + #end + @EqualsAndHashCode(callSuper = true) // Lombok注解:生成equals和hashCode方法,包含父类字段 + @Data // Lombok注解:自动生成getter、setter、toString等方法 + @AllArgsConstructor // Lombok注解:生成全参构造方法 + @NoArgsConstructor // Lombok注解:生成无参构造方法 + public class ${ClassName} extends ${Entity} + { + private static final long serialVersionUID = 1L; // 序列化版本号 + + #foreach ($column in $columns) + #if(!$table.isSuperColumn($column.javaField)) // 排除父类已定义的字段(如id、createTime等) + /** $column.columnComment */ // 字段注释,取自数据库字段说明 + #if($column.list) // 仅对列表显示字段添加Excel注解(用于导出) + #set($parentheseIndex=$column.columnComment.indexOf("(")) // 处理注释中的括号内容(如状态(0正常1停用)) + #if($parentheseIndex != -1) + #set($comment=$column.columnComment.substring(0, $parentheseIndex)) // 截取括号前的纯注释 + #else + #set($comment=$column.columnComment) + #end + #if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") // 带字典转换的Excel注解 + #elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") // 日期格式化注解 + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") // 日期类型Excel注解 + #else + @Excel(name = "${comment}") // 普通字段Excel注解 + #end + #end + private $column.javaType $column.javaField; // 字段定义(类型+名称) + + #end + #end + #if($table.sub) // 主子表场景:添加子表集合字段 + /** $table.subTable.functionName信息 */ + private List<${subClassName}> ${subclassName}List; // 子表数据列表(一对多关系) + + #end + + } diff --git a/huacai-generator/src/main/resources/vm/java/mapper.java.vm b/huacai-generator/src/main/resources/vm/java/mapper.java.vm new file mode 100644 index 0000000..7e7d7c2 --- /dev/null +++ b/huacai-generator/src/main/resources/vm/java/mapper.java.vm @@ -0,0 +1,91 @@ +package ${packageName}.mapper; + +import java.util.List; +import ${packageName}.domain.${ClassName}; +#if($table.sub) +import ${packageName}.domain.${subClassName}; +#end + +/** + * ${functionName}Mapper接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface ${ClassName}Mapper +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 删除${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); +#if($table.sub) + + /** + * 批量删除${subTable.functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 批量新增${subTable.functionName} + * + * @param ${subclassName}List ${subTable.functionName}列表 + * @return 结果 + */ + public int batch${subClassName}(List<${subClassName}> ${subclassName}List); + + + /** + * 通过${functionName}主键删除${subTable.functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}ID + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField}); +#end +} diff --git a/huacai-generator/src/main/resources/vm/java/service.java.vm b/huacai-generator/src/main/resources/vm/java/service.java.vm new file mode 100644 index 0000000..2dffb14 --- /dev/null +++ b/huacai-generator/src/main/resources/vm/java/service.java.vm @@ -0,0 +1,69 @@ +package ${packageName}.service; + +import java.util.List; +import ${packageName}.domain.${ClassName}; + +/** + * ${functionName}Service接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface I${ClassName}Service +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 批量新增${functionName} + * + * @param ${className}s ${functionName}List + * @return 结果 + */ + public int batchInsert${ClassName}(List<${ClassName}> ${className}s); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); +} diff --git a/huacai-generator/src/main/resources/vm/java/serviceImpl.java.vm b/huacai-generator/src/main/resources/vm/java/serviceImpl.java.vm new file mode 100644 index 0000000..b6d2e64 --- /dev/null +++ b/huacai-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -0,0 +1,211 @@ +package ${packageName}.service.impl; + +import java.util.List; +#foreach ($column in $columns) +#if($column.javaField == 'createTime' || $column.javaField == 'updateTime') +import com.huacai.common.utils.DateUtils; +#break +#end +#end +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +#if($table.sub) +import java.util.ArrayList; +import com.huacai.common.utils.StringUtils; +import org.springframework.transaction.annotation.Transactional; +import ${packageName}.domain.${subClassName}; +#end +import ${packageName}.mapper.${ClassName}Mapper; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.util.CollectionUtils; + +/** + * ${functionName}Service业务层处理 + * + * @author ${author} + * @date ${datetime} + */ +@Service +public class ${ClassName}ServiceImpl implements I${ClassName}Service +{ + @Autowired + private ${ClassName}Mapper ${className}Mapper; + + @Autowired + private SqlSessionFactory sqlSessionFactory; + + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + @Override + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { + return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName} + */ + @Override + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) + { + return ${className}Mapper.select${ClassName}List(${className}); + } + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int insert${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'createTime') + ${className}.setCreateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + int rows = ${className}Mapper.insert${ClassName}(${className}); + insert${subClassName}(${className}); + return rows; +#else + return ${className}Mapper.insert${ClassName}(${className}); +#end + } + + /** + * 批量新增${functionName} + * + * @param ${className}s ${functionName}List + * @return 结果 + */ + @Override + public int batchInsert${ClassName}(List<${ClassName}> ${className}s) + { + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false); + int count = 0; + if (!CollectionUtils.isEmpty(${className}s)) { + try { + for (int i = 0; i < ${className}s.size(); i++) { + int row = ${className}Mapper.insert${ClassName}(${className}s.get(i)); + // 防止内存溢出,每100次提交一次,并清除缓存 + boolean bool = (i >0 && i%100 == 0) || i == ${className}s.size() - 1; + if (bool){ + sqlSession.commit(); + sqlSession.clearCache(); + } + count = i + 1; + } + }catch (Exception e){ + e.printStackTrace(); + // 没有提交的数据可以回滚 + sqlSession.rollback(); + }finally { + sqlSession.close(); + return count; + } + } + return count; + } + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int update${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'updateTime') + ${className}.setUpdateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}()); + insert${subClassName}(${className}); +#end + return ${className}Mapper.update${ClassName}(${className}); + } + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s); + } + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } +#if($table.sub) + + /** + * 新增${subTable.functionName}信息 + * + * @param ${className} ${functionName}对象 + */ + public void insert${subClassName}(${ClassName} ${className}) + { + List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); + ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); + if (StringUtils.isNotNull(${subclassName}List)) + { + List<${subClassName}> list = new ArrayList<${subClassName}>(); + for (${subClassName} ${subclassName} : ${subclassName}List) + { + ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField}); + list.add(${subclassName}); + } + if (list.size() > 0) + { + ${className}Mapper.batch${subClassName}(list); + } + } + } +#end +} diff --git a/huacai-generator/src/main/resources/vm/java/sub-domain.java.vm b/huacai-generator/src/main/resources/vm/java/sub-domain.java.vm new file mode 100644 index 0000000..3b804b0 --- /dev/null +++ b/huacai-generator/src/main/resources/vm/java/sub-domain.java.vm @@ -0,0 +1,47 @@ +package ${packageName}.domain; + +#foreach ($import in $subImportList) +import ${import}; +#end +import com.huacai.common.annotation.Excel; +import lombok.*; +import com.huacai.common.core.domain.BaseEntity; +/** + * ${subTable.functionName}对象 ${subTableName} + * + * @author ${author} + * @date ${datetime} + */ +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ${subClassName} extends BaseEntity +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end + +} diff --git a/huacai-generator/src/main/resources/vm/js/api.js.vm b/huacai-generator/src/main/resources/vm/js/api.js.vm new file mode 100644 index 0000000..9295524 --- /dev/null +++ b/huacai-generator/src/main/resources/vm/js/api.js.vm @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询${functionName}列表 +export function list${BusinessName}(query) { + return request({ + url: '/${moduleName}/${businessName}/list', + method: 'get', + params: query + }) +} + +// 查询${functionName}详细 +export function get${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'get' + }) +} + +// 新增${functionName} +export function add${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'post', + data: data + }) +} + +// 修改${functionName} +export function update${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'put', + data: data + }) +} + +// 删除${functionName} +export function del${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'delete' + }) +} diff --git a/huacai-generator/src/main/resources/vm/sql/sql.vm b/huacai-generator/src/main/resources/vm/sql/sql.vm new file mode 100644 index 0000000..0575583 --- /dev/null +++ b/huacai-generator/src/main/resources/vm/sql/sql.vm @@ -0,0 +1,22 @@ +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null, '${functionName}菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 'admin', sysdate(), '', null, ''); \ No newline at end of file diff --git a/huacai-generator/src/main/resources/vm/vue/index-tree.vue.vm b/huacai-generator/src/main/resources/vm/vue/index-tree.vue.vm new file mode 100644 index 0000000..859f17e --- /dev/null +++ b/huacai-generator/src/main/resources/vm/vue/index-tree.vue.vm @@ -0,0 +1,503 @@ + + + diff --git a/huacai-generator/src/main/resources/vm/vue/index.vue.vm b/huacai-generator/src/main/resources/vm/vue/index.vue.vm new file mode 100644 index 0000000..d2aa807 --- /dev/null +++ b/huacai-generator/src/main/resources/vm/vue/index.vue.vm @@ -0,0 +1,712 @@ + + + diff --git a/huacai-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm b/huacai-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm new file mode 100644 index 0000000..c54d62b --- /dev/null +++ b/huacai-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm @@ -0,0 +1,474 @@ + + + diff --git a/huacai-generator/src/main/resources/vm/vue/v3/index.vue.vm b/huacai-generator/src/main/resources/vm/vue/v3/index.vue.vm new file mode 100644 index 0000000..8b25665 --- /dev/null +++ b/huacai-generator/src/main/resources/vm/vue/v3/index.vue.vm @@ -0,0 +1,590 @@ + + + diff --git a/huacai-generator/src/main/resources/vm/xml/mapper.xml.vm b/huacai-generator/src/main/resources/vm/xml/mapper.xml.vm new file mode 100644 index 0000000..4233b10 --- /dev/null +++ b/huacai-generator/src/main/resources/vm/xml/mapper.xml.vm @@ -0,0 +1,135 @@ + + + + + +#foreach ($column in $columns) + +#end + +#if($table.sub) + + + + + + +#foreach ($column in $subTable.columns) + +#end + +#end + + + select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end from ${tableName} + + + + + + + + insert into ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + $column.columnName, +#end +#end + + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + #{$column.javaField}, +#end +#end + + + + + update ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName) + $column.columnName = #{$column.javaField}, +#end +#end + + where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} in + + #{${pkColumn.javaField}} + + +#if($table.sub) + + + delete from ${subTableName} where ${subTableFkName} in + + #{${subTableFkclassName}} + + + + + delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} + + + + insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values + + (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end) + + +#end + diff --git a/huacai-generator/target/classes/com/huacai/generator/config/GenConfig.class b/huacai-generator/target/classes/com/huacai/generator/config/GenConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..3f57dd0db9f505f51e157266a9505372e9f312f6 GIT binary patch literal 1589 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 literal 0 HcmV?d00001 diff --git a/huacai-generator/target/classes/com/huacai/generator/domain/GenTable.class b/huacai-generator/target/classes/com/huacai/generator/domain/GenTable.class new file mode 100644 index 0000000000000000000000000000000000000000..eb4e12fe25d9c7036920e584dbd8d8e72c635442 GIT binary patch literal 7906 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 literal 0 HcmV?d00001 diff --git a/huacai-generator/target/classes/com/huacai/generator/domain/GenTableColumn.class b/huacai-generator/target/classes/com/huacai/generator/domain/GenTableColumn.class new file mode 100644 index 0000000000000000000000000000000000000000..44b352394f2c18a3e0af3dcd39abb041406956a0 GIT binary patch literal 7466 zcmbVR378y36@I-tGu^xCg)lit2tg1Ddy#k(iQ$nNG;lfyIv}6riP704>g*+P9PGt*Y zmXlBQSf0H+?>qjuMu{a(-toINYHV#AXd;7}bZRzf3Yi+UOJ+vwyzN?k!JRa+($255 zhH`dqAy*p9Yh(sePNQEmkULEot&2npseR-25c1_-^w{x0`R=ngAunK zEMiP2n$*h6#~kmx&CL+Bv!Fwx87dna3_3|8?G%QXGU;SG1^Z=RR znkKQ6&inR=?RN2k7n^hmZ$yjZWlBZc?G@3i(_1u}S&e3yPlGIude^pmyJy^>VGaw? zTjz|~1|c$xQZXBjj$kzC1)~o50r{4@*`UoBD_Sl#f=_@!W6%ux>8wEo^a^g)cKb_X z23-awEbOv}qv&y9cqaK&f@48oEX|LnNAfuAy*T6=omMwWE|Q3l-Rf%FzfRd-x>`Kk#2BtI>Fu-?2j43VdE&_S*#70VjcZ|*3?Mwt(#mxu ziUXw6J8Cu~(CsO0+-SSaw2c}W%JnAQz_<~-{RMydSkcGU+=x?jY%|?V@6zerCf!2s z0oLlS28J|>d&QiC7&%daxlQIPM9mi8YtsAZ{Wwl=eewiqbaZPvEqhQyFst2W((Uv? z>}7>9z5{A>!bJ@FutvoGeZ-`X(#H@tRA z)d*7ktZys)t<`OkI)Mf@>V76hb6Pm~oLJeJ_RR z=|KkDj8*k9+w-gu9H*J9kT#y*b;k=^cO00w<)v%)9hlhj;yrsbnp?XNUp_U9#d-@_ z=pWwa>kq}_F$meGs7{F(Gh>)VAwsJ7qf~yJ(TE}pGFGC-eN3xLJ%WOv}Am1c=Kwj z7;eR3V!VLW_Dm3~MeHp*3+znTlm_&QW!=zWW`L%;6jb&B|3p0aRp#`?iwDH1^ z(N{uSRUi+Mz7y&VR0Yzn1oBlikd`Qr41lbw1BCaDfqYE~q$LXELM4!|)Bezl8b+o@ zfm{M0m(~Hofx|$)K@WtsWJab&fea~ud{Yf%dKAbAfQ;4w!ja2BzNG{*JqjeJ1oF5V z$dOSXmjQ@d2M8Y>2J(aw$dOSXTa-Y)tp;*b6v$Noa&;Xbe0CYgca%VmiUQfH1oB-q zkhxJH*8|86b%5~Y!9c#J1Tr@Y<^@rj@56fcOPCTBObLFW2MoOJshL_(rqiQLJt%>xGKDVy zFvC^{k?>?~(TcJ{|g*3XEt{+Q%wmGwnxrp_qS z;wV%5B?DEa@G0cbnR-HUNoFc=sfMYasF_+4W$H;V^^{~*W{T?vX6jibQ%j;uJtH|< zWvW}v)RHot6=mvK$v~AUd=)x$rk;~rl9>uzs$uGE0TdKQ@#HWQ?E%b$xH<<)iCvQHB-x?O#K5){Zle4GsQJ1 zGxZB4Q_G@E{abRh%G3%qQ_ISO)m5wl_9U(bdW$IitQ|U7GMVXo@8K^Rauc(L4)EvnrnW?~~ z8m4}wW~x8R)UjadILWNc6u-wXQ@>U+)gNW*1j*4VQ@>FvpC46jS1KP+dvSi$ixj*# zxsDh77RFvYueP-=YU?!EI=zl9ev@NcFDRW_7j8};o@1`Ok4miZlBt;k1ic- zOujWU7)uUi2II+WW-yW5m>JZQqnSY?xhXR^HIdBWS5M~gYb1-A338(=u9j9@%zqn# zbq$}spAJ`yAl?$yACUB`i1Fz>)b9xq&uUKufPpB+fZ~9uz;m%=Z&n4*?p+p( z1dP|B0-l~6PhK@aZ|i%6whiv4YgeP5y>2^g>`1<|OgHtP^ytD^9<{)O*s5 z_=3fW*$FWvH9OH5E7Pru_1OuuA6RU3CO=fB?X&eVeLVR|elqZryc0hcTo>1NyuP=y zvrM0kWy^GTneN@mtMldu74v+&=aXjP&wE2OT^pu3noSF}5$e_)TB&WKHQHv{1X)qb z(=}S~9@veq11srx`aS)DbXr4m=oOHUzeAyleHBj$b~3DSO}d=^D2~K*D$t+k&nV*g n7CMGvuTvW(bTV|hS*L9}y@s#8^e;LaISk)f=&zX7K!5u$<}i%d literal 0 HcmV?d00001 diff --git a/huacai-generator/target/classes/com/huacai/generator/mapper/GenTableColumnMapper.class b/huacai-generator/target/classes/com/huacai/generator/mapper/GenTableColumnMapper.class new file mode 100644 index 0000000000000000000000000000000000000000..2468bed5e9cf2e188e968719d4b86a6303ae8958 GIT binary patch literal 803 zcmb7COHaZ;7@S1~!50dc;7#KJF0Kb}7!%QGNa`cv&C}9u>XLTbv|AJYng@S?Kg#%( z5*`hSa%#WX&NnmN`TqI*0&s!D5(*6GcHr~(MA$-cS85pv6GYq>VJIVhFZHV!c=9Ij z5??=z!4lRPszdQ1xF@vBp9Vu|8-}A;dh~KTm}A7<(WCGs!)eQ!G5RJ_+U>M6C^5?8 zmWoY>q1;!l7AA>^^70SLrM1+(6Zk^uocqod2^?o87jvcJ-O<=~&tW*37q)`sPM`n$ zXk~A8rDGYHMQ;qewMxi#kD;D~jxaL6sNv8B<>8p2HibQX-%_W)&*rd6P%5>hM bc^f-OYr8vn?x8w8-zWS4HR6(~@rK4P1xfSL literal 0 HcmV?d00001 diff --git a/huacai-generator/target/classes/com/huacai/generator/mapper/GenTableMapper.class b/huacai-generator/target/classes/com/huacai/generator/mapper/GenTableMapper.class new file mode 100644 index 0000000000000000000000000000000000000000..b3b41dc4b86f3fe91548a63deceb1ac6c27ef545 GIT binary patch literal 988 zcmbVLO;5r=5Pgdu2rBZWqMl8P2Yc~gz?evkhNS2LZ(dy5O)V)c>4yn_&4WL{A7z|w zZ3)mr@ihBpci+ssnfdTXRSfdH zTR+W25>sh(tz)hC$OicSIFamzxXSi)*Ie<)IsTx_Gc`n$1Q+60gT)q|r75 z!v^gaP@-+w!ZzU|p#pXYmzHoDyXpNN-S4B4;se4})DUf59Sv!lIF$D2&mN;i+zcl& G!s#y!<|eHG literal 0 HcmV?d00001 diff --git a/huacai-generator/target/classes/com/huacai/generator/service/GenTableColumnServiceImpl.class b/huacai-generator/target/classes/com/huacai/generator/service/GenTableColumnServiceImpl.class new file mode 100644 index 0000000000000000000000000000000000000000..bb5c2b28a9c078c8af0d47ad933cb2169d897e76 GIT binary patch literal 1642 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 literal 0 HcmV?d00001 diff --git a/huacai-generator/target/classes/com/huacai/generator/service/IGenTableColumnService.class b/huacai-generator/target/classes/com/huacai/generator/service/IGenTableColumnService.class new file mode 100644 index 0000000000000000000000000000000000000000..b47bfa32146ba392cbb5f15131d9368f5c9eb109 GIT binary patch literal 510 zcmb7BO-sW-5Pef?W9wHBdJs=~YA^0VZ;>7>u>wiGY5Or;jNGRjGBov4&s=v0a5L78VcE7YAW z>e3`@I6#*$oXdq2g*3T%oXu702^W*ka6-ks4E=TcCV{ACEMp%w z;psBUD#Gb+If-|=ae%1?iLG6#?sdVM&$s)2)i~E95O(;2K$p+3iyp@TM;+{O+{b_u Rg#GXL0Ee6-9IYyjzW^S#nOpz> literal 0 HcmV?d00001 diff --git a/huacai-generator/target/classes/com/huacai/generator/service/IGenTableService.class b/huacai-generator/target/classes/com/huacai/generator/service/IGenTableService.class new file mode 100644 index 0000000000000000000000000000000000000000..092aead498ac592f429cd2e477325c67b83a8109 GIT binary patch literal 1304 zcmbVM%TC)s6g>ll6bLD=@F=vDwn)i>dc&e>P^G3&gcRCEDmHAG*dsG&;)(nS%CB|7 z2k=p-*W=g$CsIvU<8$ZSJNI#X?;rl$0XW2d2~!LQw(eTj5x2RtT;U0yhuXIS;rFF2 ztg}<${pKAd+KHir7Ys``yw5GgJ=glxxe<2Auo4JGA1S*f12TTDwI&94beBudO396S z%LI%tE@)65iso6E|Zi^ z589zGJ$F(S=S6tWPcv=YH>IfawF&L}GG=_nE4f)V2WO6%2b9gAy ztQqP>2YSJ<>X5KT&T%p{fVC%$P0}H9MRt4I&s0d>wY;?IL~G=&g_{$5FZ6uTm*V!j zc1WCi!&pWM!@i8ve9n8MuG4>Uj>gqAZi#hTcJ!^MGwvK#T{E&*Oujco|r?~c628LXop2tFVU#9y-R5JXE@Di30ubmaFnsW_RbFO0}<89LY7G7s~oA3_a Z7~Wg#n)BW8+`*o~@8fvn@xf&F@h|RAdz}CP literal 0 HcmV?d00001 diff --git a/huacai-generator/target/classes/com/huacai/generator/util/GenUtils.class b/huacai-generator/target/classes/com/huacai/generator/util/GenUtils.class new file mode 100644 index 0000000000000000000000000000000000000000..cf0ef736ebbdfee036a47d612f6e14382373d8df GIT binary patch literal 5827 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select column_id, table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, create_by, create_time, update_by, update_time from gen_table_column + + + + + + + + insert into gen_table_column ( + table_id, + column_name, + column_comment, + column_type, + java_type, + java_field, + is_pk, + is_increment, + is_required, + is_insert, + is_edit, + is_list, + is_query, + query_type, + html_type, + dict_type, + sort, + create_by, + create_time + )values( + #{tableId}, + #{columnName}, + #{columnComment}, + #{columnType}, + #{javaType}, + #{javaField}, + #{isPk}, + #{isIncrement}, + #{isRequired}, + #{isInsert}, + #{isEdit}, + #{isList}, + #{isQuery}, + #{queryType}, + #{htmlType}, + #{dictType}, + #{sort}, + #{createBy}, + sysdate() + ) + + + + update gen_table_column + + column_comment = #{columnComment}, + java_type = #{javaType}, + java_field = #{javaField}, + is_insert = #{isInsert}, + is_edit = #{isEdit}, + is_list = #{isList}, + is_query = #{isQuery}, + is_required = #{isRequired}, + query_type = #{queryType}, + html_type = #{htmlType}, + dict_type = #{dictType}, + sort = #{sort}, + update_by = #{updateBy}, + update_time = sysdate() + + where column_id = #{columnId} + + + + delete from gen_table_column where table_id in + + #{tableId} + + + + + delete from gen_table_column where column_id in + + #{item.columnId} + + + + diff --git a/huacai-generator/target/classes/mapper/generator/GenTableMapper.xml b/huacai-generator/target/classes/mapper/generator/GenTableMapper.xml new file mode 100644 index 0000000..7f17e71 --- /dev/null +++ b/huacai-generator/target/classes/mapper/generator/GenTableMapper.xml @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, tpl_web_type, package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table + + + + + + + + + + + + + + + + + + insert into gen_table ( + table_name, + table_comment, + class_name, + tpl_category, + tpl_web_type, + package_name, + module_name, + business_name, + function_name, + function_author, + gen_type, + gen_path, + remark, + create_by, + create_time + )values( + #{tableName}, + #{tableComment}, + #{className}, + #{tplCategory}, + #{tplWebType}, + #{packageName}, + #{moduleName}, + #{businessName}, + #{functionName}, + #{functionAuthor}, + #{genType}, + #{genPath}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update gen_table + + table_name = #{tableName}, + table_comment = #{tableComment}, + sub_table_name = #{subTableName}, + sub_table_fk_name = #{subTableFkName}, + class_name = #{className}, + function_author = #{functionAuthor}, + gen_type = #{genType}, + gen_path = #{genPath}, + tpl_category = #{tplCategory}, + tpl_web_type = #{tplWebType}, + package_name = #{packageName}, + module_name = #{moduleName}, + business_name = #{businessName}, + function_name = #{functionName}, + options = #{options}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where table_id = #{tableId} + + + + delete from gen_table where table_id in + + #{tableId} + + + + diff --git a/huacai-generator/target/classes/vm/java/controller.java.vm b/huacai-generator/target/classes/vm/java/controller.java.vm new file mode 100644 index 0000000..971bc58 --- /dev/null +++ b/huacai-generator/target/classes/vm/java/controller.java.vm @@ -0,0 +1,143 @@ +package ${packageName}.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.huacai.common.annotation.Log; +import com.huacai.common.core.controller.BaseController; +import com.huacai.common.core.domain.AjaxResult; +import com.huacai.common.enums.BusinessType; +import java.io.InputStream; +import org.springframework.web.multipart.MultipartFile; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import com.huacai.common.utils.poi.ExcelUtil; +#if($table.crud || $table.sub) +import com.huacai.common.core.page.TableDataInfo; +#elseif($table.tree) +#end + +/** + * ${functionName}Controller + * + * @author ${author} + * @date ${datetime} + */ +@RestController +@RequestMapping("/${moduleName}/${businessName}") +public class ${ClassName}Controller extends BaseController +{ + @Autowired + private I${ClassName}Service ${className}Service; + + /** + * 查询${functionName}列表 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')") + @GetMapping("/list") +#if($table.crud || $table.sub) + public TableDataInfo list(${ClassName} ${className}) + { + startPage(); + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return getDataTable(list); + } +#elseif($table.tree) + public AjaxResult list(${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return success(list); + } +#end + + /** + * 导出${functionName}列表 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')") + @Log(title = "${functionName}", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, ${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + util.exportExcel(response, list, "${functionName}数据"); + } + + /** + * 下载模板 + */ + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) + { + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + util.importTemplateExcel(response, "${functionName}数据"); + } + + /** + * 导入数据 + */ + @Log(title = "${functionName}", businessType = BusinessType.IMPORT) + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:import')") + @PostMapping("/importData") + public AjaxResult importData(MultipartFile file) throws Exception + { + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + InputStream inputStream = file.getInputStream(); + List<${ClassName}> list = util.importExcel(inputStream ); + inputStream.close(); + int count = ${className}Service.batchInsert${ClassName}(list); + return AjaxResult.success("导入成功" + count + "条信息!"); + } + + /** + * 获取${functionName}详细信息 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:query')") + @GetMapping(value = "/{${pkColumn.javaField}}") + public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) + { + return success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); + } + + /** + * 新增${functionName} + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:add')") + @Log(title = "${functionName}", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.insert${ClassName}(${className})); + } + + /** + * 修改${functionName} + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:edit')") + @Log(title = "${functionName}", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.update${ClassName}(${className})); + } + + /** + * 删除${functionName} + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:remove')") + @Log(title = "${functionName}", businessType = BusinessType.DELETE) + @DeleteMapping("/{${pkColumn.javaField}s}") + public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) + { + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s)); + } +} diff --git a/huacai-generator/target/classes/vm/java/domain.java.vm b/huacai-generator/target/classes/vm/java/domain.java.vm new file mode 100644 index 0000000..c608b41 --- /dev/null +++ b/huacai-generator/target/classes/vm/java/domain.java.vm @@ -0,0 +1,61 @@ +package ${packageName}.domain; + +#foreach ($import in $importList) +import ${import}; +#end +import com.huacai.common.annotation.Excel; +#if($table.crud || $table.sub) +#elseif($table.tree) +#end +import lombok.*; +import com.huacai.common.core.domain.BaseEntity; + +/** + * ${functionName}对象 ${tableName} + * + * @author ${author} + * @date ${datetime} + */ +#if($table.crud || $table.sub) +#set($Entity="BaseEntity") +#elseif($table.tree) +#set($Entity="TreeEntity") +#end +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ${ClassName} extends ${Entity} +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#if($table.sub) + /** $table.subTable.functionName信息 */ + private List<${subClassName}> ${subclassName}List; + +#end + +} diff --git a/huacai-generator/target/classes/vm/java/mapper.java.vm b/huacai-generator/target/classes/vm/java/mapper.java.vm new file mode 100644 index 0000000..7e7d7c2 --- /dev/null +++ b/huacai-generator/target/classes/vm/java/mapper.java.vm @@ -0,0 +1,91 @@ +package ${packageName}.mapper; + +import java.util.List; +import ${packageName}.domain.${ClassName}; +#if($table.sub) +import ${packageName}.domain.${subClassName}; +#end + +/** + * ${functionName}Mapper接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface ${ClassName}Mapper +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 删除${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); +#if($table.sub) + + /** + * 批量删除${subTable.functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 批量新增${subTable.functionName} + * + * @param ${subclassName}List ${subTable.functionName}列表 + * @return 结果 + */ + public int batch${subClassName}(List<${subClassName}> ${subclassName}List); + + + /** + * 通过${functionName}主键删除${subTable.functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}ID + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField}); +#end +} diff --git a/huacai-generator/target/classes/vm/java/service.java.vm b/huacai-generator/target/classes/vm/java/service.java.vm new file mode 100644 index 0000000..2dffb14 --- /dev/null +++ b/huacai-generator/target/classes/vm/java/service.java.vm @@ -0,0 +1,69 @@ +package ${packageName}.service; + +import java.util.List; +import ${packageName}.domain.${ClassName}; + +/** + * ${functionName}Service接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface I${ClassName}Service +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 批量新增${functionName} + * + * @param ${className}s ${functionName}List + * @return 结果 + */ + public int batchInsert${ClassName}(List<${ClassName}> ${className}s); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); +} diff --git a/huacai-generator/target/classes/vm/java/serviceImpl.java.vm b/huacai-generator/target/classes/vm/java/serviceImpl.java.vm new file mode 100644 index 0000000..b6d2e64 --- /dev/null +++ b/huacai-generator/target/classes/vm/java/serviceImpl.java.vm @@ -0,0 +1,211 @@ +package ${packageName}.service.impl; + +import java.util.List; +#foreach ($column in $columns) +#if($column.javaField == 'createTime' || $column.javaField == 'updateTime') +import com.huacai.common.utils.DateUtils; +#break +#end +#end +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +#if($table.sub) +import java.util.ArrayList; +import com.huacai.common.utils.StringUtils; +import org.springframework.transaction.annotation.Transactional; +import ${packageName}.domain.${subClassName}; +#end +import ${packageName}.mapper.${ClassName}Mapper; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.util.CollectionUtils; + +/** + * ${functionName}Service业务层处理 + * + * @author ${author} + * @date ${datetime} + */ +@Service +public class ${ClassName}ServiceImpl implements I${ClassName}Service +{ + @Autowired + private ${ClassName}Mapper ${className}Mapper; + + @Autowired + private SqlSessionFactory sqlSessionFactory; + + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + @Override + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { + return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName} + */ + @Override + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) + { + return ${className}Mapper.select${ClassName}List(${className}); + } + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int insert${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'createTime') + ${className}.setCreateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + int rows = ${className}Mapper.insert${ClassName}(${className}); + insert${subClassName}(${className}); + return rows; +#else + return ${className}Mapper.insert${ClassName}(${className}); +#end + } + + /** + * 批量新增${functionName} + * + * @param ${className}s ${functionName}List + * @return 结果 + */ + @Override + public int batchInsert${ClassName}(List<${ClassName}> ${className}s) + { + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false); + int count = 0; + if (!CollectionUtils.isEmpty(${className}s)) { + try { + for (int i = 0; i < ${className}s.size(); i++) { + int row = ${className}Mapper.insert${ClassName}(${className}s.get(i)); + // 防止内存溢出,每100次提交一次,并清除缓存 + boolean bool = (i >0 && i%100 == 0) || i == ${className}s.size() - 1; + if (bool){ + sqlSession.commit(); + sqlSession.clearCache(); + } + count = i + 1; + } + }catch (Exception e){ + e.printStackTrace(); + // 没有提交的数据可以回滚 + sqlSession.rollback(); + }finally { + sqlSession.close(); + return count; + } + } + return count; + } + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int update${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'updateTime') + ${className}.setUpdateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}()); + insert${subClassName}(${className}); +#end + return ${className}Mapper.update${ClassName}(${className}); + } + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s); + } + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } +#if($table.sub) + + /** + * 新增${subTable.functionName}信息 + * + * @param ${className} ${functionName}对象 + */ + public void insert${subClassName}(${ClassName} ${className}) + { + List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); + ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); + if (StringUtils.isNotNull(${subclassName}List)) + { + List<${subClassName}> list = new ArrayList<${subClassName}>(); + for (${subClassName} ${subclassName} : ${subclassName}List) + { + ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField}); + list.add(${subclassName}); + } + if (list.size() > 0) + { + ${className}Mapper.batch${subClassName}(list); + } + } + } +#end +} diff --git a/huacai-generator/target/classes/vm/java/sub-domain.java.vm b/huacai-generator/target/classes/vm/java/sub-domain.java.vm new file mode 100644 index 0000000..3b804b0 --- /dev/null +++ b/huacai-generator/target/classes/vm/java/sub-domain.java.vm @@ -0,0 +1,47 @@ +package ${packageName}.domain; + +#foreach ($import in $subImportList) +import ${import}; +#end +import com.huacai.common.annotation.Excel; +import lombok.*; +import com.huacai.common.core.domain.BaseEntity; +/** + * ${subTable.functionName}对象 ${subTableName} + * + * @author ${author} + * @date ${datetime} + */ +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ${subClassName} extends BaseEntity +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end + +} diff --git a/huacai-generator/target/classes/vm/js/api.js.vm b/huacai-generator/target/classes/vm/js/api.js.vm new file mode 100644 index 0000000..9295524 --- /dev/null +++ b/huacai-generator/target/classes/vm/js/api.js.vm @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询${functionName}列表 +export function list${BusinessName}(query) { + return request({ + url: '/${moduleName}/${businessName}/list', + method: 'get', + params: query + }) +} + +// 查询${functionName}详细 +export function get${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'get' + }) +} + +// 新增${functionName} +export function add${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'post', + data: data + }) +} + +// 修改${functionName} +export function update${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'put', + data: data + }) +} + +// 删除${functionName} +export function del${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'delete' + }) +} diff --git a/huacai-generator/target/classes/vm/sql/sql.vm b/huacai-generator/target/classes/vm/sql/sql.vm new file mode 100644 index 0000000..0575583 --- /dev/null +++ b/huacai-generator/target/classes/vm/sql/sql.vm @@ -0,0 +1,22 @@ +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null, '${functionName}菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 'admin', sysdate(), '', null, ''); \ No newline at end of file diff --git a/huacai-generator/target/classes/vm/vue/index-tree.vue.vm b/huacai-generator/target/classes/vm/vue/index-tree.vue.vm new file mode 100644 index 0000000..859f17e --- /dev/null +++ b/huacai-generator/target/classes/vm/vue/index-tree.vue.vm @@ -0,0 +1,503 @@ + + + diff --git a/huacai-generator/target/classes/vm/vue/index.vue.vm b/huacai-generator/target/classes/vm/vue/index.vue.vm new file mode 100644 index 0000000..d2aa807 --- /dev/null +++ b/huacai-generator/target/classes/vm/vue/index.vue.vm @@ -0,0 +1,712 @@ + + + diff --git a/huacai-generator/target/classes/vm/vue/v3/index-tree.vue.vm b/huacai-generator/target/classes/vm/vue/v3/index-tree.vue.vm new file mode 100644 index 0000000..c54d62b --- /dev/null +++ b/huacai-generator/target/classes/vm/vue/v3/index-tree.vue.vm @@ -0,0 +1,474 @@ + + + diff --git a/huacai-generator/target/classes/vm/vue/v3/index.vue.vm b/huacai-generator/target/classes/vm/vue/v3/index.vue.vm new file mode 100644 index 0000000..8b25665 --- /dev/null +++ b/huacai-generator/target/classes/vm/vue/v3/index.vue.vm @@ -0,0 +1,590 @@ + + + diff --git a/huacai-generator/target/classes/vm/xml/mapper.xml.vm b/huacai-generator/target/classes/vm/xml/mapper.xml.vm new file mode 100644 index 0000000..4233b10 --- /dev/null +++ b/huacai-generator/target/classes/vm/xml/mapper.xml.vm @@ -0,0 +1,135 @@ + + + + + +#foreach ($column in $columns) + +#end + +#if($table.sub) + + + + + + +#foreach ($column in $subTable.columns) + +#end + +#end + + + select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end from ${tableName} + + + + + + + + insert into ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + $column.columnName, +#end +#end + + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + #{$column.javaField}, +#end +#end + + + + + update ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName) + $column.columnName = #{$column.javaField}, +#end +#end + + where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} in + + #{${pkColumn.javaField}} + + +#if($table.sub) + + + delete from ${subTableName} where ${subTableFkName} in + + #{${subTableFkclassName}} + + + + + delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} + + + + insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values + + (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end) + + +#end + diff --git a/huacai-quartz/pom.xml b/huacai-quartz/pom.xml new file mode 100644 index 0000000..728e9e1 --- /dev/null +++ b/huacai-quartz/pom.xml @@ -0,0 +1,40 @@ + + + + huacai + com.huacai + 3.8.7 + + 4.0.0 + + huacai-quartz + + + quartz定时任务 + + + + + + + org.quartz-scheduler + quartz + + + com.mchange + c3p0 + + + + + + + com.huacai + huacai-common + + + + + diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/config/ScheduleConfig.java b/huacai-quartz/src/main/java/com/huacai/quartz/config/ScheduleConfig.java new file mode 100644 index 0000000..9852b95 --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/config/ScheduleConfig.java @@ -0,0 +1,57 @@ +//package com.huacai.quartz.config; +// +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.scheduling.quartz.SchedulerFactoryBean; +//import javax.sql.DataSource; +//import java.util.Properties; +// +///** +// * 定时任务配置(单机部署建议删除此类和qrtz数据库表,默认走内存会最高效) +// * +// * @author huacai +// */ +//@Configuration +//public class ScheduleConfig +//{ +// @Bean +// public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) +// { +// SchedulerFactoryBean factory = new SchedulerFactoryBean(); +// factory.setDataSource(dataSource); +// +// // quartz参数 +// Properties prop = new Properties(); +// prop.put("org.quartz.scheduler.instanceName", "huacaiScheduler"); +// prop.put("org.quartz.scheduler.instanceId", "AUTO"); +// // 线程池配置 +// prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); +// prop.put("org.quartz.threadPool.threadCount", "20"); +// prop.put("org.quartz.threadPool.threadPriority", "5"); +// // JobStore配置 +// prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore"); +// // 集群配置 +// prop.put("org.quartz.jobStore.isClustered", "true"); +// prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000"); +// prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "10"); +// prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true"); +// +// // sqlserver 启用 +// // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?"); +// prop.put("org.quartz.jobStore.misfireThreshold", "12000"); +// prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); +// factory.setQuartzProperties(prop); +// +// factory.setSchedulerName("huacaiScheduler"); +// // 延时启动 +// factory.setStartupDelay(1); +// factory.setApplicationContextSchedulerContextKey("applicationContextKey"); +// // 可选,QuartzScheduler +// // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 +// factory.setOverwriteExistingJobs(true); +// // 设置自动启动,默认为true +// factory.setAutoStartup(true); +// +// return factory; +// } +//} diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/controller/SysJobController.java b/huacai-quartz/src/main/java/com/huacai/quartz/controller/SysJobController.java new file mode 100644 index 0000000..d11aa3b --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/controller/SysJobController.java @@ -0,0 +1,235 @@ +package com.huacai.quartz.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.huacai.common.annotation.Log; +import com.huacai.common.constant.Constants; +import com.huacai.common.core.controller.BaseController; +import com.huacai.common.core.domain.AjaxResult; +import com.huacai.common.core.page.TableDataInfo; +import com.huacai.common.enums.BusinessType; +import com.huacai.common.exception.job.TaskException; +import com.huacai.common.utils.StringUtils; +import com.huacai.common.utils.poi.ExcelUtil; +import com.huacai.quartz.domain.SysJob; +import com.huacai.quartz.service.ISysJobService; +import com.huacai.quartz.util.CronUtils; +import com.huacai.quartz.util.ScheduleUtils; + +/** + * 调度任务信息操作处理控制器 + * 负责定时任务的CRUD、状态切换、立即执行等核心功能接口 + * + * @author huacai + */ +@RestController +@RequestMapping("/monitor/job") +public class SysJobController extends BaseController +{ + @Autowired + private ISysJobService jobService; + + /** + * 查询定时任务列表 + * 支持分页和条件过滤,需"monitor:job:list"权限 + * + * @param sysJob 包含查询条件的任务对象 + * @return 分页后的任务列表数据 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJob sysJob) + { + startPage(); // 开启分页(继承自BaseController) + List list = jobService.selectJobList(sysJob); // 调用服务层查询列表 + return getDataTable(list); // 包装成分页响应对象 + } + + /** + * 导出定时任务列表 + * 将查询结果导出为Excel文件,需"monitor:job:export"权限 + * + * @param response HTTP响应对象 + * @param sysJob 查询条件 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:export')") + @Log(title = "定时任务", businessType = BusinessType.EXPORT) // 记录操作日志 + @PostMapping("/export") + public void export(HttpServletResponse response, SysJob sysJob) + { + List list = jobService.selectJobList(sysJob); + ExcelUtil util = new ExcelUtil(SysJob.class); + util.exportExcel(response, list, "定时任务"); // 导出Excel + } + + /** + * 获取定时任务详细信息 + * 根据任务ID查询单条任务详情,需"monitor:job:query"权限 + * + * @param jobId 任务ID + * @return 包含任务详情的响应对象 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobId}") + public AjaxResult getInfo(@PathVariable("jobId") Long jobId) + { + return success(jobService.selectJobById(jobId)); // 调用服务层查询详情 + } + + /** + * 新增定时任务 + * 包含严格的参数校验(Cron表达式、调用目标安全性等),需"monitor:job:add"权限 + * + * @param job 待新增的任务对象 + * @return 新增结果响应 + * @throws SchedulerException 调度器异常 + * @throws TaskException 任务处理异常 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:add')") + @Log(title = "定时任务", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysJob job) throws SchedulerException, TaskException + { + // 校验Cron表达式合法性 + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + // 安全校验:禁止RMI调用 + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + // 安全校验:禁止LDAP调用 + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); + } + // 安全校验:禁止HTTP调用 + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + // 安全校验:禁止包含违规字符串 + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + // 安全校验:必须在白名单内 + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + + job.setCreateBy(getUsername()); // 设置创建人(从当前登录用户获取) + return toAjax(jobService.insertJob(job)); // 调用服务层新增任务 + } + + /** + * 修改定时任务 + * 包含与新增相同的参数校验逻辑,需"monitor:job:edit"权限 + * + * @param job 待修改的任务对象 + * @return 修改结果响应 + * @throws SchedulerException 调度器异常 + * @throws TaskException 任务处理异常 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:edit')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysJob job) throws SchedulerException, TaskException + { + // 与新增任务相同的校验逻辑(Cron表达式+安全校验) + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + + job.setUpdateBy(getUsername()); // 设置更新人 + return toAjax(jobService.updateJob(job)); // 调用服务层更新任务 + } + + /** + * 定时任务状态修改 + * 用于启用/停用定时任务,需"monitor:job:changeStatus"权限 + * + * @param job 包含任务ID和目标状态的对象 + * @return 状态修改结果响应 + * @throws SchedulerException 调度器异常 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException + { + SysJob newJob = jobService.selectJobById(job.getJobId()); // 先查询原任务 + newJob.setStatus(job.getStatus()); // 更新状态 + return toAjax(jobService.changeStatus(newJob)); // 调用服务层修改状态 + } + + /** + * 定时任务立即执行一次 + * 无视Cron表达式,立即触发任务执行,需"monitor:job:changeStatus"权限 + * + * @param job 包含任务ID的对象 + * @return 执行结果响应 + * @throws SchedulerException 调度器异常 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/run") + public AjaxResult run(@RequestBody SysJob job) throws SchedulerException + { + boolean result = jobService.run(job); // 调用服务层立即执行 + return result ? success() : error("任务不存在或已过期!"); + } + + /** + * 删除定时任务 + * 批量删除指定ID的任务,需"monitor:job:remove"权限 + * + * @param jobIds 任务ID数组 + * @return 删除结果响应 + * @throws SchedulerException 调度器异常 + * @throws TaskException 任务处理异常 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobIds}") + public AjaxResult remove(@PathVariable Long[] jobIds) throws SchedulerException, TaskException + { + jobService.deleteJobByIds(jobIds); // 调用服务层批量删除 + return success(); + } +} diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/controller/SysJobLogController.java b/huacai-quartz/src/main/java/com/huacai/quartz/controller/SysJobLogController.java new file mode 100644 index 0000000..cb70a92 --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/controller/SysJobLogController.java @@ -0,0 +1,111 @@ +package com.huacai.quartz.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.huacai.common.annotation.Log; +import com.huacai.common.core.controller.BaseController; +import com.huacai.common.core.domain.AjaxResult; +import com.huacai.common.core.page.TableDataInfo; +import com.huacai.common.enums.BusinessType; +import com.huacai.common.utils.poi.ExcelUtil; +import com.huacai.quartz.domain.SysJobLog; +import com.huacai.quartz.service.ISysJobLogService; + +/** + * 调度日志操作处理控制器 + * 负责定时任务执行日志的查询、导出、删除、清空等管理功能 + * + * @author huacai + */ +@RestController +@RequestMapping("/monitor/jobLog") +public class SysJobLogController extends BaseController +{ + @Autowired + private ISysJobLogService jobLogService; + + /** + * 查询定时任务调度日志列表 + * 支持分页和条件过滤,需"monitor:job:list"权限 + * + * @param sysJobLog 包含查询条件的日志对象 + * @return 分页后的日志列表数据 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJobLog sysJobLog) + { + startPage(); // 开启分页(继承自BaseController) + List list = jobLogService.selectJobLogList(sysJobLog); // 调用服务层查询日志列表 + return getDataTable(list); // 包装成分页响应对象 + } + + /** + * 导出定时任务调度日志列表 + * 将查询结果导出为Excel文件,需"monitor:job:export"权限 + * + * @param response HTTP响应对象 + * @param sysJobLog 查询条件 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:export')") + @Log(title = "任务调度日志", businessType = BusinessType.EXPORT) // 记录导出操作日志 + @PostMapping("/export") + public void export(HttpServletResponse response, SysJobLog sysJobLog) + { + List list = jobLogService.selectJobLogList(sysJobLog); + ExcelUtil util = new ExcelUtil(SysJobLog.class); + util.exportExcel(response, list, "调度日志"); // 导出Excel文件 + } + + /** + * 根据调度日志ID获取详细信息 + * 查询单条日志的详细执行记录,需"monitor:job:query"权限 + * + * @param jobLogId 日志ID + * @return 包含日志详情的响应对象 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobLogId}") + public AjaxResult getInfo(@PathVariable Long jobLogId) + { + return success(jobLogService.selectJobLogById(jobLogId)); // 调用服务层查询日志详情 + } + + /** + * 批量删除定时任务调度日志 + * 删除指定ID的日志记录,需"monitor:job:remove"权限 + * + * @param jobLogIds 日志ID数组 + * @return 删除结果响应 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务调度日志", businessType = BusinessType.DELETE) // 记录删除操作日志 + @DeleteMapping("/{jobLogIds}") + public AjaxResult remove(@PathVariable Long[] jobLogIds) + { + return toAjax(jobLogService.deleteJobLogByIds(jobLogIds)); // 调用服务层批量删除 + } + + /** + * 清空所有定时任务调度日志 + * 一次性删除系统中所有任务调度日志,需"monitor:job:remove"权限 + * + * @return 清空结果响应 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "调度日志", businessType = BusinessType.CLEAN) // 记录清空操作日志 + @DeleteMapping("/clean") + public AjaxResult clean() + { + jobLogService.cleanJobLog(); // 调用服务层清空日志 + return success(); + } +} \ No newline at end of file diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/domain/SysJob.java b/huacai-quartz/src/main/java/com/huacai/quartz/domain/SysJob.java new file mode 100644 index 0000000..5f65a02 --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/domain/SysJob.java @@ -0,0 +1,192 @@ +package com.huacai.quartz.domain; + +import java.util.Date; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.huacai.common.annotation.Excel; +import com.huacai.common.annotation.Excel.ColumnType; +import com.huacai.common.constant.ScheduleConstants; +import com.huacai.common.core.domain.BaseEntity; +import com.huacai.common.utils.StringUtils; +import com.huacai.quartz.util.CronUtils; + +/** + * 定时任务调度实体类(对应表sys_job) + * 封装定时任务的基本信息、调度规则及执行状态 + * + * @author huacai + */ +public class SysJob extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 任务ID(主键) */ + @Excel(name = "任务序号", cellType = ColumnType.NUMERIC) // Excel导出时的配置 + private Long jobId; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名(用于分类管理任务) */ + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串(任务执行的目标方法,如类名.方法名) */ + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** cron执行表达式(任务调度的时间规则) */ + @Excel(name = "执行表达式 ") + private String cronExpression; + + /** + * cron计划策略(任务错过执行时的处理策略) + * 默认值为ScheduleConstants.MISFIRE_DEFAULT(默认策略) + */ + @Excel(name = "计划策略 ", readConverterExp = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行") + private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT; + + /** 是否并发执行(0允许 1禁止) */ + @Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止") + private String concurrent; + + /** 任务状态(0正常 1暂停) */ + @Excel(name = "任务状态", readConverterExp = "0=正常,1=暂停") + private String status; + + // ------------------- Getter和Setter方法 ------------------- + public Long getJobId() + { + return jobId; + } + + public void setJobId(Long jobId) + { + this.jobId = jobId; + } + + /** + * 任务名称校验:非空且长度不超过64字符 + */ + @NotBlank(message = "任务名称不能为空") + @Size(min = 0, max = 64, message = "任务名称不能超过64个字符") + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + /** + * 调用目标字符串校验:非空且长度不超过500字符 + */ + @NotBlank(message = "调用目标字符串不能为空") + @Size(min = 0, max = 500, message = "调用目标字符串长度不能超过500个字符") + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + /** + * Cron表达式校验:非空且长度不超过255字符 + */ + @NotBlank(message = "Cron执行表达式不能为空") + @Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符") + public String getCronExpression() + { + return cronExpression; + } + + public void setCronExpression(String cronExpression) + { + this.cronExpression = cronExpression; + } + + /** + * 计算下次执行时间(通过Cron表达式解析) + * 前端展示用,不对应数据库字段 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public Date getNextValidTime() + { + if (StringUtils.isNotEmpty(cronExpression)) + { + return CronUtils.getNextExecution(cronExpression); + } + return null; + } + + public String getMisfirePolicy() + { + return misfirePolicy; + } + + public void setMisfirePolicy(String misfirePolicy) + { + this.misfirePolicy = misfirePolicy; + } + + public String getConcurrent() + { + return concurrent; + } + + public void setConcurrent(String concurrent) + { + this.concurrent = concurrent; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + /** + * 重写toString方法,便于日志打印和调试 + */ + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("jobId", getJobId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("cronExpression", getCronExpression()) + .append("nextValidTime", getNextValidTime()) + .append("misfirePolicy", getMisfirePolicy()) + .append("concurrent", getConcurrent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} \ No newline at end of file diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/domain/SysJobLog.java b/huacai-quartz/src/main/java/com/huacai/quartz/domain/SysJobLog.java new file mode 100644 index 0000000..d061fd7 --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/domain/SysJobLog.java @@ -0,0 +1,234 @@ + +package com.huacai.quartz.domain; + +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.huacai.common.annotation.Excel; +import com.huacai.common.core.domain.BaseEntity; + +/** + * 定时任务调度日志表对应的实体类(映射表sys_job_log) + * 用于记录定时任务每次执行的详细日志信息,包括执行状态、时间、异常等 + * + * @author huacai + */ +public class SysJobLog extends BaseEntity // 继承基础实体类,复用通用字段(如创建时间等) +{ + // 序列化版本号,用于对象序列化时的版本控制 + private static final long serialVersionUID = 1L; + + /** 日志ID(主键) */ + @Excel(name = "日志序号") // Excel导出时的列名配置 + private Long jobLogId; + + /** 任务名称(关联sys_job表的jobName) */ + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名(关联sys_job表的jobGroup,用于分类) */ + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串(记录任务执行的目标方法,与任务定义一致) */ + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** 日志信息(记录任务执行的简要描述,如"执行成功") */ + @Excel(name = "日志信息") + private String jobMessage; + + /** 执行状态(0正常 1失败,用于快速标识任务执行结果) */ + @Excel(name = "执行状态", readConverterExp = "0=正常,1=失败") // 导出时自动转换编码为文字描述 + private String status; + + /** 异常信息(当执行失败时,记录具体的异常堆栈信息,便于排查问题) */ + @Excel(name = "异常信息") + private String exceptionInfo; + + /** 开始时间(任务实际开始执行的时间) */ + private Date startTime; + + /** 停止时间(任务实际执行结束的时间) */ + private Date stopTime; + + /** + * 获取日志ID + * @return 日志ID + */ + public Long getJobLogId() + { + return jobLogId; + } + + /** + * 设置日志ID + * @param jobLogId 日志ID + */ + public void setJobLogId(Long jobLogId) + { + this.jobLogId = jobLogId; + } + + /** + * 获取任务名称 + * @return 任务名称 + */ + public String getJobName() + { + return jobName; + } + + /** + * 设置任务名称 + * @param jobName 任务名称 + */ + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + /** + * 获取任务组名 + * @return 任务组名 + */ + public String getJobGroup() + { + return jobGroup; + } + + /** + * 设置任务组名 + * @param jobGroup 任务组名 + */ + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + /** + * 获取调用目标字符串 + * @return 调用目标字符串 + */ + public String getInvokeTarget() + { + return invokeTarget; + } + + /** + * 设置调用目标字符串 + * @param invokeTarget 调用目标字符串 + */ + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + /** + * 获取日志信息 + * @return 日志信息 + */ + public String getJobMessage() + { + return jobMessage; + } + + /** + * 设置日志信息 + * @param jobMessage 日志信息 + */ + public void setJobMessage(String jobMessage) + { + this.jobMessage = jobMessage; + } + + /** + * 获取执行状态(0正常 1失败) + * @return 执行状态 + */ + public String getStatus() + { + return status; + } + + /** + * 设置执行状态(0正常 1失败) + * @param status 执行状态 + */ + public void setStatus(String status) + { + this.status = status; + } + + /** + * 获取异常信息 + * @return 异常信息 + */ + public String getExceptionInfo() + { + return exceptionInfo; + } + + /** + * 设置异常信息 + * @param exceptionInfo 异常信息 + */ + public void setExceptionInfo(String exceptionInfo) + { + this.exceptionInfo = exceptionInfo; + } + + /** + * 获取开始时间 + * @return 开始时间 + */ + public Date getStartTime() + { + return startTime; + } + + /** + * 设置开始时间 + * @param startTime 开始时间 + */ + public void setStartTime(Date startTime) + { + this.startTime = startTime; + } + + /** + * 获取停止时间 + * @return 停止时间 + */ + public Date getStopTime() + { + return stopTime; + } + + /** + * 设置停止时间 + * @param stopTime 停止时间 + */ + public void setStopTime(Date stopTime) + { + this.stopTime = stopTime; + } + + /** + * 重写toString方法,使用多行动格式输出对象信息 + * 便于日志打印和调试时查看对象详情 + */ + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobLogId", getJobLogId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("jobMessage", getJobMessage()) + .append("status", getStatus()) + .append("exceptionInfo", getExceptionInfo()) + .append("startTime", getStartTime()) + .append("stopTime", getStopTime()) + .toString(); + } +} diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/mapper/SysJobLogMapper.java b/huacai-quartz/src/main/java/com/huacai/quartz/mapper/SysJobLogMapper.java new file mode 100644 index 0000000..9c4633c --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/mapper/SysJobLogMapper.java @@ -0,0 +1,64 @@ +package com.huacai.quartz.mapper; + +import java.util.List; +import com.huacai.quartz.domain.SysJobLog; + +/** + * 调度任务日志信息 数据层 + * + * @author huacai + */ +public interface SysJobLogMapper +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 查询所有调度任务日志 + * + * @return 调度任务日志列表 + */ + public List selectJobLogAll(); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + * @return 结果 + */ + public int insertJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的数据ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/mapper/SysJobMapper.java b/huacai-quartz/src/main/java/com/huacai/quartz/mapper/SysJobMapper.java new file mode 100644 index 0000000..cf1970e --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/mapper/SysJobMapper.java @@ -0,0 +1,67 @@ +package com.huacai.quartz.mapper; + +import java.util.List; +import com.huacai.quartz.domain.SysJob; + +/** + * 调度任务信息 数据层 + * + * @author huacai + */ +public interface SysJobMapper +{ + /** + * 查询调度任务日志集合 + * + * @param job 调度信息 + * @return 操作日志集合 + */ + public List selectJobList(SysJob job); + + /** + * 查询所有调度任务 + * + * @return 调度任务列表 + */ + public List selectJobAll(); + + /** + * 通过调度ID查询调度任务信息 + * + * @param jobId 调度ID + * @return 角色对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 通过调度ID删除调度任务信息 + * + * @param jobId 调度ID + * @return 结果 + */ + public int deleteJobById(Long jobId); + + /** + * 批量删除调度任务信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteJobByIds(Long[] ids); + + /** + * 修改调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int updateJob(SysJob job); + + /** + * 新增调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int insertJob(SysJob job); +} diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/service/ISysJobLogService.java b/huacai-quartz/src/main/java/com/huacai/quartz/service/ISysJobLogService.java new file mode 100644 index 0000000..8c302a1 --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/service/ISysJobLogService.java @@ -0,0 +1,56 @@ +package com.huacai.quartz.service; + +import java.util.List; +import com.huacai.quartz.domain.SysJobLog; + +/** + * 定时任务调度日志信息信息 服务层 + * + * @author huacai + */ +public interface ISysJobLogService +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + public void addJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的日志ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/service/ISysJobService.java b/huacai-quartz/src/main/java/com/huacai/quartz/service/ISysJobService.java new file mode 100644 index 0000000..3fabf12 --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/service/ISysJobService.java @@ -0,0 +1,102 @@ +package com.huacai.quartz.service; + +import java.util.List; +import org.quartz.SchedulerException; +import com.huacai.common.exception.job.TaskException; +import com.huacai.quartz.domain.SysJob; + +/** + * 定时任务调度信息信息 服务层 + * + * @author huacai + */ +public interface ISysJobService +{ + /** + * 获取quartz调度器的计划任务 + * + * @param job 调度信息 + * @return 调度任务集合 + */ + public List selectJobList(SysJob job); + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 暂停任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int pauseJob(SysJob job) throws SchedulerException; + + /** + * 恢复任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int resumeJob(SysJob job) throws SchedulerException; + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + * @return 结果 + */ + public int deleteJob(SysJob job) throws SchedulerException; + + /** + * 批量删除调度信息 + * + * @param jobIds 需要删除的任务ID + * @return 结果 + */ + public void deleteJobByIds(Long[] jobIds) throws SchedulerException; + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + * @return 结果 + */ + public int changeStatus(SysJob job) throws SchedulerException; + + /** + * 立即运行任务 + * + * @param job 调度信息 + * @return 结果 + */ + public boolean run(SysJob job) throws SchedulerException; + + /** + * 新增任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int insertJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 更新任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int updateJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + public boolean checkCronExpressionIsValid(String cronExpression); +} diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/service/impl/SysJobLogServiceImpl.java b/huacai-quartz/src/main/java/com/huacai/quartz/service/impl/SysJobLogServiceImpl.java new file mode 100644 index 0000000..715cc2d --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/service/impl/SysJobLogServiceImpl.java @@ -0,0 +1,87 @@ +package com.huacai.quartz.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.huacai.quartz.domain.SysJobLog; +import com.huacai.quartz.mapper.SysJobLogMapper; +import com.huacai.quartz.service.ISysJobLogService; + +/** + * 定时任务调度日志信息 服务层 + * + * @author huacai + */ +@Service +public class SysJobLogServiceImpl implements ISysJobLogService +{ + @Autowired + private SysJobLogMapper jobLogMapper; + + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + @Override + public List selectJobLogList(SysJobLog jobLog) + { + return jobLogMapper.selectJobLogList(jobLog); + } + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + @Override + public SysJobLog selectJobLogById(Long jobLogId) + { + return jobLogMapper.selectJobLogById(jobLogId); + } + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + @Override + public void addJobLog(SysJobLog jobLog) + { + jobLogMapper.insertJobLog(jobLog); + } + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteJobLogByIds(Long[] logIds) + { + return jobLogMapper.deleteJobLogByIds(logIds); + } + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + */ + @Override + public int deleteJobLogById(Long jobId) + { + return jobLogMapper.deleteJobLogById(jobId); + } + + /** + * 清空任务日志 + */ + @Override + public void cleanJobLog() + { + jobLogMapper.cleanJobLog(); + } +} diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/service/impl/SysJobServiceImpl.java b/huacai-quartz/src/main/java/com/huacai/quartz/service/impl/SysJobServiceImpl.java new file mode 100644 index 0000000..624ccf6 --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/service/impl/SysJobServiceImpl.java @@ -0,0 +1,296 @@ +package com.huacai.quartz.service.impl; + +import java.util.List; +import javax.annotation.PostConstruct; +import org.quartz.JobDataMap; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.huacai.common.constant.ScheduleConstants; +import com.huacai.common.exception.job.TaskException; +import com.huacai.quartz.domain.SysJob; +import com.huacai.quartz.mapper.SysJobMapper; +import com.huacai.quartz.service.ISysJobService; +import com.huacai.quartz.util.CronUtils; +import com.huacai.quartz.util.ScheduleUtils; + +/** + * 定时任务调度信息服务层实现类 + * 负责定时任务的CRUD、状态管理、调度控制等核心业务逻辑 + * + * @author huacai + */ +@Service // 标识为Spring服务组件 +public class SysJobServiceImpl implements ISysJobService // 实现定时任务服务接口 +{ + @Autowired // 自动注入Quartz调度器实例 + private Scheduler scheduler; + + @Autowired // 自动注入任务数据访问层接口 + private SysJobMapper jobMapper; + + /** + * 项目启动时初始化定时器 + * 作用:将数据库中配置的任务同步到Quartz调度器中 + * 注意:禁止手动修改数据库中的任务ID和任务组名,否则会导致数据不一致 + * @throws SchedulerException Quartz调度器异常 + * @throws TaskException 任务处理异常 + */ + @PostConstruct // 标注此方法在Bean初始化后自动执行 + public void init() throws SchedulerException, TaskException + { + scheduler.clear(); // 清空调度器中已有的任务(防止重复加载) + List jobList = jobMapper.selectJobAll(); // 查询数据库中所有任务配置 + for (SysJob job : jobList) + { + // 为每个任务创建Quartz调度任务 + ScheduleUtils.createScheduleJob(scheduler, job); + } + } + + /** + * 获取quartz调度器的计划任务列表 + * 支持根据条件查询任务 + * @param job 包含查询条件的任务对象 + * @return 符合条件的任务列表 + */ + @Override + public List selectJobList(SysJob job) + { + // 调用Mapper层查询任务列表 + return jobMapper.selectJobList(job); + } + + /** + * 通过调度任务ID查询调度信息 + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + @Override + public SysJob selectJobById(Long jobId) + { + // 调用Mapper层根据ID查询任务 + return jobMapper.selectJobById(jobId); + } + + /** + * 暂停任务 + * 同时更新数据库状态和Quartz调度器中的任务状态 + * @param job 调度信息 + * @return 影响的行数 + * @throws SchedulerException Quartz调度器异常 + */ + @Override + @Transactional(rollbackFor = Exception.class) // 声明事务,发生异常时回滚 + public int pauseJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); // 获取任务ID + String jobGroup = job.getJobGroup(); // 获取任务组名 + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); // 设置任务状态为"暂停" + int rows = jobMapper.updateJob(job); // 更新数据库中的任务状态 + if (rows > 0) // 数据库更新成功后,同步更新调度器中的任务状态 + { + // 暂停Quartz中的任务 + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; // 返回数据库更新影响的行数 + } + + /** + * 恢复任务 + * 同时更新数据库状态和Quartz调度器中的任务状态 + * @param job 调度信息 + * @return 影响的行数 + * @throws SchedulerException Quartz调度器异常 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int resumeJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.NORMAL.getValue()); // 设置任务状态为"正常" + int rows = jobMapper.updateJob(job); // 更新数据库状态 + if (rows > 0) // 同步更新调度器状态 + { + // 恢复Quartz中的任务 + scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 删除任务 + * 同时删除数据库记录和Quartz调度器中的任务(包括关联的trigger) + * @param job 调度信息 + * @return 影响的行数 + * @throws SchedulerException Quartz调度器异常 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + int rows = jobMapper.deleteJobById(jobId); // 从数据库删除任务 + if (rows > 0) // 同步删除调度器中的任务 + { + // 删除Quartz中的任务(会自动删除关联的trigger) + scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 批量删除调度信息 + * @param jobIds 需要删除的任务ID数组 + * @throws SchedulerException Quartz调度器异常 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteJobByIds(Long[] jobIds) throws SchedulerException + { + for (Long jobId : jobIds) // 遍历每个任务ID + { + SysJob job = jobMapper.selectJobById(jobId); // 查询任务详情 + deleteJob(job); // 调用单条删除方法 + } + } + + /** + * 任务调度状态修改(切换启用/暂停状态) + * @param job 调度信息(包含目标状态) + * @return 影响的行数 + * @throws SchedulerException Quartz调度器异常 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int changeStatus(SysJob job) throws SchedulerException + { + int rows = 0; + String status = job.getStatus(); // 获取目标状态 + if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) + { + // 目标状态为"正常",执行恢复任务操作 + rows = resumeJob(job); + } + else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) + { + // 目标状态为"暂停",执行暂停任务操作 + rows = pauseJob(job); + } + return rows; + } + + /** + * 立即运行任务(无视Cron表达式,触发一次执行) + * @param job 调度信息 + * @return 执行结果(true表示成功,false表示任务不存在) + * @throws SchedulerException Quartz调度器异常 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean run(SysJob job) throws SchedulerException + { + boolean result = false; + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + // 查询任务完整信息(包含最新配置) + SysJob properties = selectJobById(job.getJobId()); + // 构建任务参数映射(用于传递任务信息到执行器) + JobDataMap dataMap = new JobDataMap(); + dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties); + // 获取Quartz中的任务标识 + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + // 检查任务是否存在 + if (scheduler.checkExists(jobKey)) + { + result = true; + // 立即触发任务执行,并传递参数 + scheduler.triggerJob(jobKey, dataMap); + } + return result; + } + + /** + * 新增任务 + * 同时保存到数据库和Quartz调度器 + * @param job 调度信息 + * @return 影响的行数 + * @throws SchedulerException Quartz调度器异常 + * @throws TaskException 任务处理异常(如Cron表达式无效) + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int insertJob(SysJob job) throws SchedulerException, TaskException + { + // 新任务默认状态为"暂停"(需手动启用) + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.insertJob(job); // 保存到数据库 + if (rows > 0) // 数据库保存成功后,同步到调度器 + { + // 创建Quartz调度任务 + ScheduleUtils.createScheduleJob(scheduler, job); + } + return rows; + } + + /** + * 更新任务的配置信息(包括时间表达式等) + * @param job 调度信息(包含更新后的配置) + * @return 影响的行数 + * @throws SchedulerException Quartz调度器异常 + * @throws TaskException 任务处理异常 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateJob(SysJob job) throws SchedulerException, TaskException + { + // 查询数据库中当前任务的旧配置(主要获取旧的任务组名) + SysJob properties = selectJobById(job.getJobId()); + int rows = jobMapper.updateJob(job); // 更新数据库配置 + if (rows > 0) // 数据库更新成功后,同步更新调度器 + { + // 更新Quartz中的任务配置(使用旧的任务组名进行定位) + updateSchedulerJob(job, properties.getJobGroup()); + } + return rows; + } + + /** + * 内部方法:更新Quartz调度器中的任务配置 + * @param job 包含新配置的任务对象 + * @param jobGroup 旧的任务组名(用于定位原有任务) + * @throws SchedulerException Quartz调度器异常 + * @throws TaskException 任务处理异常 + */ + public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException + { + Long jobId = job.getJobId(); + // 获取原有任务的标识 + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + // 检查任务是否存在 + if (scheduler.checkExists(jobKey)) + { + // 先删除原有任务(避免配置冲突) + scheduler.deleteJob(jobKey); + } + // 基于新配置创建任务 + ScheduleUtils.createScheduleJob(scheduler, job); + } + + /** + * 校验cron表达式是否有效 + * @param cronExpression 待校验的Cron表达式 + * @return 校验结果(true表示有效,false表示无效) + */ + @Override + public boolean checkCronExpressionIsValid(String cronExpression) + { + // 调用工具类进行Cron表达式校验 + return CronUtils.isValid(cronExpression); + } +} diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/task/RyTask.java b/huacai-quartz/src/main/java/com/huacai/quartz/task/RyTask.java new file mode 100644 index 0000000..10dabab --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/task/RyTask.java @@ -0,0 +1,37 @@ +package com.huacai.quartz.task; + +import org.springframework.stereotype.Component; +import com.huacai.common.utils.StringUtils; + +/** + * 定时任务调度 + * + * @author huacai + * @optimization 郑长川(花菜菜) + */ +@Component("ryTask") +public class RyTask +{ +// @Resource +// private IJdzsService jdzsService; +// +// public void selectJdzs(String jdzsId) { +// Jdzs jdzs = jdzsService.selectJdzsByJdzsId(jdzsId); +// System.out.println(jdzs); +// } + + public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) + { + System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i)); + } + + public void ryParams(String params) + { + System.out.println("执行有参方法:" + params); + } + + public void ryNoParams() + { + System.out.println("执行无参方法"); + } +} diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/util/AbstractQuartzJob.java b/huacai-quartz/src/main/java/com/huacai/quartz/util/AbstractQuartzJob.java new file mode 100644 index 0000000..d24cba0 --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/util/AbstractQuartzJob.java @@ -0,0 +1,141 @@ +package com.huacai.quartz.util; + +import java.util.Date; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.huacai.common.constant.Constants; +import com.huacai.common.constant.ScheduleConstants; +import com.huacai.common.utils.ExceptionUtil; +import com.huacai.common.utils.StringUtils; +import com.huacai.common.utils.bean.BeanUtils; +import com.huacai.common.utils.spring.SpringUtils; +import com.huacai.quartz.domain.SysJob; +import com.huacai.quartz.domain.SysJobLog; +import com.huacai.quartz.service.ISysJobLogService; + +/** + * 抽象quartz任务执行的抽象基类 + * 封装任务执行的通用流程(前置处理、后置处理、日志记录等),具体任务逻辑由子类实现 + * + * @author huacai + */ +public abstract class AbstractQuartzJob implements Job // 实现Quartz的Job接口,成为可调度任务 +{ + // 日志记录器,用于记录任务执行过程中的日志信息 + private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class); + + /** + * 线程本地变量 + * 用于存储当前线程中任务的开始时间,避免多线程环境下的时间混乱 + */ + private static ThreadLocal threadLocal = new ThreadLocal<>(); + + /** + * 任务执行的入口方法(实现Job接口的execute方法) + * 定义任务执行的完整流程:前置处理 -> 核心执行 -> 后置处理(含异常处理) + * + * @param context Quartz的任务执行上下文,包含任务相关的环境信息 + * @throws JobExecutionException 任务执行异常 + */ + @Override + public void execute(JobExecutionContext context) throws JobExecutionException + { + // 创建任务对象,用于接收上下文传递的任务配置 + SysJob sysJob = new SysJob(); + // 从上下文的参数映射中获取任务配置,并复制到sysJob对象 + // ScheduleConstants.TASK_PROPERTIES是存储任务配置的键 + BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES)); + try + { + // 执行前置处理(如记录开始时间) + before(context, sysJob); + // 若任务配置有效,则执行核心任务逻辑 + if (sysJob != null) + { + doExecute(context, sysJob); // 抽象方法,由子类实现具体业务 + } + // 执行后置处理(如记录成功日志),无异常 + after(context, sysJob, null); + } + catch (Exception e) + { + // 记录任务执行异常日志 + log.error("任务执行异常 - :", e); + // 执行后置处理(如记录失败日志),传入异常对象 + after(context, sysJob, e); + } + } + + /** + * 任务执行前的前置处理 + * 目前主要功能:记录任务开始时间到线程本地变量 + * + * @param context 任务执行上下文 + * @param sysJob 系统计划任务配置 + */ + protected void before(JobExecutionContext context, SysJob sysJob) + { + // 存储当前时间(任务开始时间)到线程本地变量 + threadLocal.set(new Date()); + } + + /** + * 任务执行后的后置处理 + * 主要功能:记录任务执行日志(包括执行时间、耗时、状态、异常信息等) + * + * @param context 任务执行上下文 + * @param sysJob 系统计划任务配置 + * @param e 执行过程中抛出的异常(无异常则为null) + */ + protected void after(JobExecutionContext context, SysJob sysJob, Exception e) + { + // 从线程本地变量获取任务开始时间 + Date startTime = threadLocal.get(); + // 移除线程本地变量中的值,避免内存泄漏 + threadLocal.remove(); + + // 创建任务日志对象,记录本次执行详情 + final SysJobLog sysJobLog = new SysJobLog(); + // 设置日志的基本信息(关联的任务名称、组名、调用目标) + sysJobLog.setJobName(sysJob.getJobName()); + sysJobLog.setJobGroup(sysJob.getJobGroup()); + sysJobLog.setInvokeTarget(sysJob.getInvokeTarget()); + // 设置开始时间和结束时间 + sysJobLog.setStartTime(startTime); + sysJobLog.setStopTime(new Date()); + // 计算任务执行耗时(毫秒) + long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime(); + sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒"); + + // 根据是否有异常设置任务执行状态 + if (e != null) + { + // 有异常:状态为失败(Constants.FAIL) + sysJobLog.setStatus(Constants.FAIL); + // 获取异常信息并截断(避免日志过长) + String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000); + sysJobLog.setExceptionInfo(errorMsg); + } + else + { + // 无异常:状态为成功(Constants.SUCCESS) + sysJobLog.setStatus(Constants.SUCCESS); + } + + // 通过Spring工具类获取日志服务Bean,将日志写入数据库 + SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); + } + + /** + * 任务执行的核心方法,由子类具体实现 + * 子类需根据业务需求重写此方法,实现实际的任务逻辑 + * + * @param context 任务执行上下文 + * @param sysJob 系统计划任务配置 + * @throws Exception 执行过程中可能抛出的异常 + */ + protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception; +} \ No newline at end of file diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/util/CronUtils.java b/huacai-quartz/src/main/java/com/huacai/quartz/util/CronUtils.java new file mode 100644 index 0000000..0c52cd4 --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/util/CronUtils.java @@ -0,0 +1,73 @@ +package com.huacai.quartz.util; + +import java.text.ParseException; +import java.util.Date; +import org.quartz.CronExpression; + +/** + * cron表达式工具类 + * 提供Cron表达式的有效性校验、错误信息获取及下次执行时间计算等功能 + * + * @author huacai + * + */ +public class CronUtils +{ + /** + * 校验给定的Cron表达式是否有效 + * Cron表达式是Quartz定时任务的时间规则表达式,格式错误会导致任务无法调度 + * + * @param cronExpression 需要校验的Cron表达式字符串 + * @return boolean 表达式有效返回true,无效返回false + */ + public static boolean isValid(String cronExpression) + { + // 调用Quartz的CronExpression工具类进行有效性校验 + return CronExpression.isValidExpression(cronExpression); + } + + /** + * 获取Cron表达式的无效原因描述 + * 当表达式无效时,返回具体的错误信息(如格式错误位置),便于问题排查 + * + * @param cronExpression 需要校验的Cron表达式字符串 + * @return String 无效时返回错误描述,有效时返回null + */ + public static String getInvalidMessage(String cronExpression) + { + try + { + // 尝试创建CronExpression对象,若成功则表达式有效 + new CronExpression(cronExpression); + return null; // 有效,返回null + } + catch (ParseException pe) + { + // 解析失败,返回异常信息(包含具体错误原因) + return pe.getMessage(); + } + } + + /** + * 根据给定的Cron表达式计算下一次执行时间 + * 从当前系统时间开始,计算符合Cron规则的最近一次执行时间 + * + * @param cronExpression Cron表达式字符串 + * @return Date 下次执行时间对象,若表达式无效则抛出异常 + */ + public static Date getNextExecution(String cronExpression) + { + try + { + // 创建CronExpression对象(已隐含表达式有效性校验) + CronExpression cron = new CronExpression(cronExpression); + // 计算当前时间之后的下一个有效执行时间 + return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis())); + } + catch (ParseException e) + { + // 表达式解析失败时,包装为运行时异常抛出 + throw new IllegalArgumentException(e.getMessage()); + } + } +} diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/util/JobInvokeUtil.java b/huacai-quartz/src/main/java/com/huacai/quartz/util/JobInvokeUtil.java new file mode 100644 index 0000000..7bd28df --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/util/JobInvokeUtil.java @@ -0,0 +1,238 @@ +package com.huacai.quartz.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; +import com.huacai.common.utils.StringUtils; +import com.huacai.common.utils.spring.SpringUtils; +import com.huacai.quartz.domain.SysJob; + +/** + * 任务执行工具类 + * 负责解析定时任务的调用目标(invokeTarget),通过反射机制执行目标方法 + * 支持Spring容器中的Bean方法和普通Java类的方法调用 + * + * @author huacai + */ +public class JobInvokeUtil +{ + /** + * 执行定时任务的目标方法 + * 根据任务配置中的invokeTarget解析出Bean名称、方法名和参数,通过反射执行 + * + * @param sysJob 系统任务对象,包含调用目标信息(invokeTarget) + * @throws Exception 执行过程中可能抛出的异常(如类找不到、方法不存在等) + */ + public static void invokeMethod(SysJob sysJob) throws Exception + { + // 获取任务配置的调用目标字符串(格式如:"beanName.methodName(param1, param2)") + String invokeTarget = sysJob.getInvokeTarget(); + // 解析出Bean名称(或类全名) + String beanName = getBeanName(invokeTarget); + // 解析出方法名称 + String methodName = getMethodName(invokeTarget); + // 解析出方法参数列表(包含参数值和参数类型) + List methodParams = getMethodParams(invokeTarget); + + // 判断Bean名称是否为完整类名(含包名,如"com.huacai.service.impl.MyService") + if (!isValidClassName(beanName)) + { + // 非完整类名:从Spring容器中获取Bean实例 + Object bean = SpringUtils.getBean(beanName); + // 调用Bean的目标方法 + invokeMethod(bean, methodName, methodParams); + } + else + { + // 完整类名:通过反射创建类实例(要求类有默认构造方法) + Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance(); + // 调用类的目标方法 + invokeMethod(bean, methodName, methodParams); + } + } + + /** + * 反射调用目标对象的指定方法 + * 根据方法名和参数列表,通过反射执行目标方法 + * + * @param bean 目标对象实例 + * @param methodName 方法名称 + * @param methodParams 方法参数列表(每个元素是包含参数值和类型的数组) + * @throws NoSuchMethodException 方法不存在时抛出 + * @throws SecurityException 安全权限异常 + * @throws IllegalAccessException 方法访问权限不足时抛出 + * @throws IllegalArgumentException 参数不匹配时抛出 + * @throws InvocationTargetException 方法执行内部抛出异常时包装后抛出 + */ + private static void invokeMethod(Object bean, String methodName, List methodParams) + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException + { + // 判断是否有方法参数 + if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) + { + // 获取方法参数类型数组 + Class[] paramTypes = getMethodParamsType(methodParams); + // 根据方法名和参数类型获取方法对象 + Method method = bean.getClass().getMethod(methodName, paramTypes); + // 获取方法参数值数组 + Object[] paramValues = getMethodParamsValue(methodParams); + // 调用方法并传入参数 + method.invoke(bean, paramValues); + } + else + { + // 无参数:直接根据方法名获取无参方法 + Method method = bean.getClass().getMethod(methodName); + // 调用无参方法 + method.invoke(bean); + } + } + + /** + * 校验字符串是否为完整的类名(含包名) + * 规则:包含至少两个"."(如"com.huacai.MyClass"含两个".") + * + * @param invokeTarget 待校验的字符串(通常是Bean名称或类名) + * @return true=是完整类名,false=不是 + */ + public static boolean isValidClassName(String invokeTarget) + { + // 统计字符串中"."的数量,大于1则认为是完整类名 + return StringUtils.countMatches(invokeTarget, ".") > 1; + } + + /** + * 从调用目标字符串中解析出Bean名称(或类全名) + * 调用目标格式:"beanName.methodName(param...)" 或 "className.methodName(param...)" + * 解析逻辑:截取"("之前的部分,再截取最后一个"."之前的部分 + * + * @param invokeTarget 调用目标字符串 + * @return 解析出的Bean名称或类全名 + */ + public static String getBeanName(String invokeTarget) + { + // 截取"("之前的部分(如"beanName.methodName") + String beanName = StringUtils.substringBefore(invokeTarget, "("); + // 截取最后一个"."之前的部分(如"beanName") + return StringUtils.substringBeforeLast(beanName, "."); + } + + /** + * 从调用目标字符串中解析出方法名称 + * 调用目标格式:"beanName.methodName(param...)" + * 解析逻辑:截取"("之前的部分,再截取最后一个"."之后的部分 + * + * @param invokeTarget 调用目标字符串 + * @return 解析出的方法名称 + */ + public static String getMethodName(String invokeTarget) + { + // 截取"("之前的部分(如"beanName.methodName") + String methodName = StringUtils.substringBefore(invokeTarget, "("); + // 截取最后一个"."之后的部分(如"methodName") + return StringUtils.substringAfterLast(methodName, "."); + } + + /** + * 从调用目标字符串中解析出方法参数列表 + * 支持字符串、布尔值、长整数、双精度浮点数和整数类型的参数解析 + * + * @param invokeTarget 调用目标字符串 + * @return 方法参数列表,每个元素是{参数值, 参数类型}的数组;无参数则返回null + */ + public static List getMethodParams(String invokeTarget) + { + // 截取"("和")"之间的部分(如"param1, param2") + String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")"); + // 无参数时返回null + if (StringUtils.isEmpty(methodStr)) + { + return null; + } + + // 按逗号分割参数(处理引号内的逗号不分割) + String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)"); + // 存储参数信息的列表 + List classs = new LinkedList<>(); + + for (int i = 0; i < methodParams.length; i++) + { + // 去除参数前后的空格 + String str = StringUtils.trimToEmpty(methodParams[i]); + + // 字符串类型:以单引号或双引号开头 + if (StringUtils.startsWithAny(str, "'", "\"")) + { + // 截取引号内的内容作为参数值,类型为String + classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class }); + } + // 布尔类型:值为true或false(不区分大小写) + else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) + { + // 转换为Boolean类型 + classs.add(new Object[] { Boolean.valueOf(str), Boolean.class }); + } + // 长整数类型:以L结尾(如123L) + else if (StringUtils.endsWith(str, "L")) + { + // 截取L之前的部分转换为Long + classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class }); + } + // 双精度浮点类型:以D结尾(如3.14D) + else if (StringUtils.endsWith(str, "D")) + { + // 截取D之前的部分转换为Double + classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class }); + } + // 默认为整数类型 + else + { + // 转换为Integer + classs.add(new Object[] { Integer.valueOf(str), Integer.class }); + } + } + return classs; + } + + /** + * 从参数列表中提取参数类型数组 + * + * @param methodParams 方法参数列表(每个元素是{参数值, 参数类型}的数组) + * @return 参数类型数组 + */ + public static Class[] getMethodParamsType(List methodParams) + { + // 创建与参数数量相同的Class数组 + Class[] classs = new Class[methodParams.size()]; + int index = 0; + // 遍历参数列表,提取每个参数的类型 + for (Object[] os : methodParams) + { + classs[index] = (Class) os[1]; + index++; + } + return classs; + } + + /** + * 从参数列表中提取参数值数组 + * + * @param methodParams 方法参数列表(每个元素是{参数值, 参数类型}的数组) + * @return 参数值数组 + */ + public static Object[] getMethodParamsValue(List methodParams) + { + // 创建与参数数量相同的Object数组 + Object[] classs = new Object[methodParams.size()]; + int index = 0; + // 遍历参数列表,提取每个参数的值 + for (Object[] os : methodParams) + { + classs[index] = (Object) os[0]; + index++; + } + return classs; + } +} \ No newline at end of file diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/util/QuartzDisallowConcurrentExecution.java b/huacai-quartz/src/main/java/com/huacai/quartz/util/QuartzDisallowConcurrentExecution.java new file mode 100644 index 0000000..834a83f --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/util/QuartzDisallowConcurrentExecution.java @@ -0,0 +1,33 @@ +package com.huacai.quartz.util; + +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; +import com.huacai.quartz.domain.SysJob; + +/** + * 定时任务处理类(禁止并发执行) + * 继承抽象任务基类,实现具体执行逻辑,且通过注解限制同一任务并发执行 + * + * @author huacai + * + */ +// Quartz注解:禁止同一任务实例并发执行 +// 若任务执行时间超过Cron表达式的间隔,下一次执行会等待当前执行完成后再触发 +@DisallowConcurrentExecution +public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob +{ + /** + * 任务执行的核心方法(实现抽象基类的抽象方法) + * 调用任务执行工具类,执行任务配置中指定的目标方法 + * + * @param context Quartz任务执行上下文,包含任务环境信息 + * @param sysJob 系统任务配置对象,包含调用目标等信息 + * @throws Exception 执行过程中可能抛出的异常(会被基类捕获并记录日志) + */ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + // 调用工具类执行任务的目标方法(反射调用) + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/util/QuartzJobExecution.java b/huacai-quartz/src/main/java/com/huacai/quartz/util/QuartzJobExecution.java new file mode 100644 index 0000000..6c2f5c8 --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/util/QuartzJobExecution.java @@ -0,0 +1,28 @@ +package com.huacai.quartz.util; + +import org.quartz.JobExecutionContext; +import com.huacai.quartz.domain.SysJob; + +/** + * 定时任务处理类(允许并发执行) + * 继承抽象任务基类,实现具体执行逻辑,默认支持同一任务的并发执行 + * + * @author huacai + */ +public class QuartzJobExecution extends AbstractQuartzJob +{ + /** + * 任务执行的核心方法(实现抽象基类的抽象方法) + * 负责调用任务执行工具类,执行具体的业务逻辑 + * + * @param context Quartz任务执行上下文,包含任务调度的环境信息(如触发时间、任务数据等) + * @param sysJob 系统任务配置对象,包含任务的调用目标、参数等核心信息 + * @throws Exception 执行过程中可能抛出的异常(会被父类捕获并记录到日志中) + */ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + // 调用任务执行工具类,通过反射执行sysJob中配置的目标方法 + JobInvokeUtil.invokeMethod(sysJob); + } +} \ No newline at end of file diff --git a/huacai-quartz/src/main/java/com/huacai/quartz/util/ScheduleUtils.java b/huacai-quartz/src/main/java/com/huacai/quartz/util/ScheduleUtils.java new file mode 100644 index 0000000..95cc4aa --- /dev/null +++ b/huacai-quartz/src/main/java/com/huacai/quartz/util/ScheduleUtils.java @@ -0,0 +1,192 @@ +package com.huacai.quartz.util; + +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; +import com.huacai.common.constant.Constants; +import com.huacai.common.constant.ScheduleConstants; +import com.huacai.common.exception.job.TaskException; +import com.huacai.common.exception.job.TaskException.Code; +import com.huacai.common.utils.StringUtils; +import com.huacai.common.utils.spring.SpringUtils; +import com.huacai.quartz.domain.SysJob; + +/** + * 定时任务工具类 + * 提供Quartz定时任务的创建、触发键/任务键生成、策略处理、白名单校验等核心功能 + * + * @author huacai + */ +public class ScheduleUtils +{ + /** + * 根据任务配置获取对应的Quartz任务执行类 + * 依据任务是否允许并发执行,返回不同的实现类 + * + * @param sysJob 执行计划(包含并发执行配置) + * @return 具体的Quartz任务执行类(允许并发/禁止并发) + */ + private static Class getQuartzJobClass(SysJob sysJob) + { + // 判断任务是否允许并发执行("0"表示允许,其他值表示禁止) + boolean isConcurrent = "0".equals(sysJob.getConcurrent()); + // 允许并发返回普通执行类,禁止并发返回带并发控制的执行类 + return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class; + } + + /** + * 构建任务触发器的唯一标识(TriggerKey) + * TriggerKey由任务ID和任务组名组成,用于唯一标识一个触发器 + * + * @param jobId 任务ID + * @param jobGroup 任务组名 + * @return 触发器唯一标识对象 + */ + public static TriggerKey getTriggerKey(Long jobId, String jobGroup) + { + // 触发器名称格式:TASK_CLASS_NAME + 任务ID(TASK_CLASS_NAME为常量前缀) + return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 构建任务的唯一标识(JobKey) + * JobKey由任务ID和任务组名组成,用于唯一标识一个任务 + * + * @param jobId 任务ID + * @param jobGroup 任务组名 + * @return 任务唯一标识对象 + */ + public static JobKey getJobKey(Long jobId, String jobGroup) + { + // 任务名称格式:TASK_CLASS_NAME + 任务ID(与触发器名称对应) + return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 创建定时任务并注册到Quartz调度器 + * 包含任务详情构建、触发器配置、参数传递、任务状态设置等完整流程 + * + * @param scheduler Quartz调度器实例 + * @param job 任务配置信息(包含Cron表达式、状态等) + * @throws SchedulerException Quartz调度器异常 + * @throws TaskException 任务配置异常(如Cron表达式无效) + */ + public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException + { + // 获取任务对应的执行类(允许/禁止并发) + Class jobClass = getQuartzJobClass(job); + + // 从任务配置中提取ID和组名 + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + + // 构建任务详情对象(JobDetail),设置唯一标识 + JobDetail jobDetail = JobBuilder.newJob(jobClass) + .withIdentity(getJobKey(jobId, jobGroup)) // 关联任务唯一标识 + .build(); + + // 构建Cron表达式调度器(根据任务配置的Cron表达式) + CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); + // 处理任务错过执行时的策略(根据任务配置的misfirePolicy) + cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder); + + // 构建触发器(Trigger),关联Cron调度策略和唯一标识 + CronTrigger trigger = TriggerBuilder.newTrigger() + .withIdentity(getTriggerKey(jobId, jobGroup)) // 关联触发器唯一标识 + .withSchedule(cronScheduleBuilder) // 关联Cron调度策略 + .build(); + + // 向任务详情中存入参数(任务配置信息),供执行时使用 + jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job); + + // 检查任务是否已存在,若存在则先删除(避免重复注册) + if (scheduler.checkExists(getJobKey(jobId, jobGroup))) + { + scheduler.deleteJob(getJobKey(jobId, jobGroup)); + } + + // 检查任务的Cron表达式是否有有效的下次执行时间(防止任务已过期) + if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression()))) + { + // 将任务详情和触发器注册到调度器 + scheduler.scheduleJob(jobDetail, trigger); + } + + // 若任务状态为"暂停",则注册后立即暂停任务 + if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + } + + /** + * 设置定时任务的错过执行策略(Misfire Policy) + * 根据任务配置的策略类型,配置Cron调度器的错过处理方式 + * + * @param job 任务配置(包含misfirePolicy策略) + * @param cb Cron调度器构建器 + * @return 配置后的Cron调度器构建器 + * @throws TaskException 若策略不支持则抛出异常 + */ + public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb) + throws TaskException + { + // 根据任务的misfirePolicy选择对应的处理策略 + switch (job.getMisfirePolicy()) + { + case ScheduleConstants.MISFIRE_DEFAULT: + // 默认策略:遵循Quartz的默认处理方式 + return cb; + case ScheduleConstants.MISFIRE_IGNORE_MISFIRES: + // 忽略所有错过的执行,按正常调度继续 + return cb.withMisfireHandlingInstructionIgnoreMisfires(); + case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED: + // 立即执行一次错过的任务,然后按正常调度继续 + return cb.withMisfireHandlingInstructionFireAndProceed(); + case ScheduleConstants.MISFIRE_DO_NOTHING: + // 不处理错过的执行,等待下一个正常调度时间 + return cb.withMisfireHandlingInstructionDoNothing(); + default: + // 未知策略:抛出配置错误异常 + throw new TaskException("The task misfire policy '" + job.getMisfirePolicy() + + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR); + } + } + + /** + * 检查任务调用目标是否在白名单内(安全校验) + * 防止恶意调用危险类或方法,限制任务只能调用指定包路径下的类 + * + * @param invokeTarget 任务调用目标字符串(如"beanName.method(param)") + * @return 若在白名单内返回true,否则返回false + */ + public static boolean whiteList(String invokeTarget) + { + // 提取调用目标中的包名部分(方法名之前的内容) + String packageName = StringUtils.substringBefore(invokeTarget, "("); + // 统计包名中的"."数量,判断是否为完整类名(含包路径) + int count = StringUtils.countMatches(packageName, "."); + + if (count > 1) + { + // 完整类名:直接检查是否包含在白名单字符串中 + return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR); + } + else + { + // 非完整类名(Spring Bean名称):获取Bean的实际包路径 + Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]); + String beanPackageName = obj.getClass().getPackage().getName(); + // 检查包路径是否在白名单内,且不包含危险字符串 + return StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_WHITELIST_STR) + && !StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR); + } + } +} diff --git a/huacai-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml b/huacai-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml new file mode 100644 index 0000000..a445ed5 --- /dev/null +++ b/huacai-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, create_time + from sys_job_log + + + + + + + + + + + + + + delete from sys_job_log where job_log_id = #{jobLogId} + + + + + delete from sys_job_log where job_log_id in + + #{jobLogId} + + + + + + truncate table sys_job_log + + + + + insert into sys_job_log( + job_log_id, + job_name, + job_group, + invoke_target, + job_message, + status, + exception_info, + create_time + )values( + #{jobLogId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{jobMessage}, + #{status}, + #{exceptionInfo}, + sysdate() + ) + + + \ No newline at end of file diff --git a/huacai-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml b/huacai-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml new file mode 100644 index 0000000..82b5292 --- /dev/null +++ b/huacai-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark + from sys_job + + + + + + + + + + + + + + delete from sys_job where job_id = #{jobId} + + + + + delete from sys_job where job_id in + + #{jobId} + + + + + + update sys_job + + job_name = #{jobName}, + job_group = #{jobGroup}, + invoke_target = #{invokeTarget}, + cron_expression = #{cronExpression}, + misfire_policy = #{misfirePolicy}, + concurrent = #{concurrent}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where job_id = #{jobId} + + + + + insert into sys_job( + job_id, + job_name, + job_group, + invoke_target, + cron_expression, + misfire_policy, + concurrent, + status, + remark, + create_by, + create_time + )values( + #{jobId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{cronExpression}, + #{misfirePolicy}, + #{concurrent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + diff --git a/huacai-quartz/target/classes/com/huacai/quartz/controller/SysJobController.class b/huacai-quartz/target/classes/com/huacai/quartz/controller/SysJobController.class new file mode 100644 index 0000000000000000000000000000000000000000..2fcd58c193829578a4b00a82be1eff9fd5946699 GIT binary patch literal 6976 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 J93ubMe*u+@MGXJ| literal 0 HcmV?d00001 diff --git a/huacai-quartz/target/classes/com/huacai/quartz/controller/SysJobLogController.class b/huacai-quartz/target/classes/com/huacai/quartz/controller/SysJobLogController.class new file mode 100644 index 0000000000000000000000000000000000000000..d047f34fbaeb7a063d7dfa53dbe1b08065e58f3d GIT binary patch literal 3756 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 literal 0 HcmV?d00001 diff --git a/huacai-quartz/target/classes/com/huacai/quartz/mapper/SysJobMapper.class b/huacai-quartz/target/classes/com/huacai/quartz/mapper/SysJobMapper.class new file mode 100644 index 0000000000000000000000000000000000000000..24ca7e68d9b46605db5a6f85946d2e2e3d826771 GIT binary patch literal 676 zcmb7?%SyvQ6o&t)+L+qb-qaURh#O~NT~!2K2$hJ7fE!oSWQbF8GZ&#gnhPJmhZ0XF z(kd;qu1@}N=9`&s&ga+r2Y?}t4AdA-+&JXVg>Z%9FNH|+Hy(;4ktug(*p1rJ2z^M}b)x;aBDlL38DpRF>==a#+9^Wspb=!Z8twKb;5Rlv(dBL>q^ekAfD zl?;#nCtR-G^NKcU&X0N>1PncE#X&BXes0EIXt06a5ZM>q48QOokGKPWmw5rc65>IFv!|?`E32mwS5Xb-9YGPYITCKi8kCniM4APEtc4VsKh>hBo_VQ?rIw0^@v6Krv&f{e zi}=`J7|z8~@J!?>zn#sMtSD5fj2Tr8C~tO&NNz zPIFP!g<^R4SCfl2lP_u15A3T|m{{y@d6CIeLTsUfutkzM{S3$LuM{9Xm3;;KvYT@@;@8i)P> zeiZ7rSqLdX0#2S8jpuvJ+}+>)C88g+6VVi-nzVi12|^0ZFM@E{4fo1jYN>cVzV^Ru zD=yNAW*9AY#iih>Fl~O?>MEHrTJS1GfX0UQ$iLSblOq$`7uxV*;K6QVI3{E|<@u)r zM&($yjmUyijGFJC@bgv7Lx4O{J-V(ZL#j`D8rHPI>^1I;W(Oj81-*>Ey@6?+QRU=H zszIi$@ezGw^u@aNquH_Ss3fGyje1~)Q$F+yh;jmZvm&mJ;+(%{v{L(1SUKECp`giV zUUsk{6=yIMUDGa@w{blOwl>&*zRrj?810Sufv|54SC3+N*S7eX@V#;BVUUOix|@(D zd&kb2ldAzFc^FXlO_6GNzgin(9%oMH?gPIP+u)@7rBhtO^8Y8epKq(g2$FEYM02>v z4^%=PA)BIE`Pr|PiJn#Z{H$kf)KdbBDy;); Q^M%?LZG#W?KV0 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 literal 0 HcmV?d00001 diff --git a/huacai-quartz/target/classes/com/huacai/quartz/task/RyTask.class b/huacai-quartz/target/classes/com/huacai/quartz/task/RyTask.class new file mode 100644 index 0000000000000000000000000000000000000000..89fd88ddb78c7a08b2a62694d4e0cdf22e331a33 GIT binary patch literal 1530 zcmaJ>?QaxC9De5B_If=8k2hO{f`!^^DeF7pVSyG&Oim;{xa49CnO=uuV0Y)-+r^xO zFPNARN&I3ZA;y3)_(hGNmLj0v{X4v?eBwVK{APErEb+o7JG1k9d4A9Dnc4a0(Vu?- zcmqdrNFZq-l}8#GhIME7Ic{3qX_z0*pAmJBA+ujP(wk;Tju)!~$YQ`iE{{Rvp9-(E zTb{5fO8Jzuu^a$NWgc^-<5fIYaJ!Vna||cDmwx>3`sL0KS36%_?0$2n``hBfdsnx2 ze!jBu(~W`+C+xhOx^A{f8yWCm+yz}eVF~ft!zq^aS_uSn-ep$JAXLUu6``@3x zki$AWZ=jIJdTd}Att;C+?ejX9CVkqNr6E+)oA&(&pGpz3;0E5xga##*2)TdCfYT!kN%}+KV#Gdh`wIJLa zwji0s2qlLacE7z8*HHU+C15h7IF= z8qT7~P>7gZxwgv0z$C-gnCzeU-1R;)J>EKN&b5!z#E_a*3xZ*7SvumVZ_f+&IG?x3 zQYfoBx2oKgdL0>4-f2m@G*W&hYKab{+y20}JlV9wG467kl0NPuK&<~6r7XdnQp{9V zP9wfPtbC#px4iFoq9NQ;vC6=xMj@6R9S3sE(my5!09_C`e}7t~gRummKnG zq+WU2Be&h_Gt-P3&bjTQs%Onrl<(HX5vjEu4AtMG&lkhcobPzj7FF4j)ZZb;Q6BfC za;Tw+vT_?{OP|)pDbi7Vs@$_C{o$@qUb`vGS!FksBOH%JPoLwKPw*Kx*j((u1l=n7 zNrAk`(72mcCe4iI^u%qjoAi;u6ph*qtic`{^I-smzCtU8SMeH+I{6EV%g{V_3r1m0 z_C6A|w$yH8Yhl|GwolT#gq?kO zY$GNk0gp{#<|>b2Ob0v|4g`wmp7&CWW;URE9`6G;O4squm~aCTTmlCvErCOrp?Q!% LX6ZfL!h4Sa|9hcZ literal 0 HcmV?d00001 diff --git a/huacai-quartz/target/classes/com/huacai/quartz/util/AbstractQuartzJob.class b/huacai-quartz/target/classes/com/huacai/quartz/util/AbstractQuartzJob.class new file mode 100644 index 0000000000000000000000000000000000000000..8805769c021a2c0b000d4179facd5bebff6fc739 GIT binary patch literal 3882 zcmbtX`+F2;6@I^DH?!Fc1eO8@+maM0xo@G=DjSOsLYp)RAsb?`RXW+7Y!)^<>FjKP zdMQ*HR0Ne1-Fe*@~A)q6ODuoq?j7cM!F|w)X-jPFQ!cnN|NN3Z|Lkg9RO~V>O z3hNWrcyugpB#dQ0TwHPE-&*Y*brjd>A;$y#jD(DGo;+T97*cNr) zyo9vH!kUJ|Xuv%hHt5)hO$v=ZaYgTlxaiK2oMRgaXP@ga8kw#gF-I-iRM_3Pz{(5# z6YE{fwan)+565O5jRK-NX?ZM6LD#IK1+5A}W7IKig#-V;IG-W9jwZ|ri6vst(9Ter zcT(xBnlSFuu?1V{%#4*%h`1`}GNaoLMSHDO%Cuu4Y-7m%Yj*bL^Ji{7H}(3N7j9mC z_U80-eSKT~f3Ch%gSX*94R6=+4s2(xNUZqpQ$f?VEh^aTcU|1E)7ezahj?hrwvHGh z8IyGyE?j#Wk%4-J5U)$Cw6P-(y<2* zGnVJgK!J1DseVpkd9@#+m+P(%JJBt<(4%k%b8ntSkwj$l>gbbVQDvLs)}*OnufjdI zi4R&hjDEqWVNgdLLkeLg72~kq7-tBUODoz;t_N9Dp44FoeJ$y`vy;|gbI4#7a^y}zM^f(8^3KAuQ5`9a zF==@gcUT06(xS$yqFGCxagExKLplx%xrmQD)`U=th;bcRSRUNDea7aT>F3#qho916 zgM~sPK2IELa*wdd>zEWaI!{VyG#q6V&AZUE)2@6vlQiuxj)*t4e*Dy}Qw8RWFn(4Te^}wp#vXq(Exdk` zqHmpl<=;~;%B7F#__+8azCNs@n6Nlh1|md3-^`7j>M%mlQTFW+MN9Q$KR>;uRoW^dQfTtq&X?

l&gIaTnZrJ#i7fa;_7cVM=;NG?vSX^ums%l znS#mAVwO0}QLw0KxFj~G;dcu4ttNv zm>!ntfO}WCuD}ecxaPFZ=PY-kd@H_#&6lArxFlEklw+`w|AbH7BfzWpDZdo1;b&wd zmv11zAJ8=m?Z9PJ_qDcML6~6{Dd3K2tZQy*Eug;jI;!2(y?m+ynx?R(|01H1``fPI zfl6?bDiU`FBLX zHWScRMDTO`f?No#2wPUa#IGnLMEdLaHC6Zx&zGU{zu2rn>(^)t;`ya;0`- z8mG61C?izB*QXHX={I;9IX8`OUBkJ$;1ztY0%8fzWj!=K&`rnnU?qAPgnih+=Fy71 zcz|~7z&<>JK@M4QoWKxHVi+%QLOYGe@G=hIES_LDc#^?txRxBGyH0a1`#t`^6>U_q z=}gI{Gew)qsps|@oW~#WC)#x#x!%B^8SszeApU~CGO|6lSG<9<8g^*t4^#yL^1p-b xm~(#{h5gN!BjR3QRit4WKfHwKEIMh+!X?B+bL9rB7X#~faXokb&ixAf<9~ZjFvb7? literal 0 HcmV?d00001 diff --git a/huacai-quartz/target/classes/com/huacai/quartz/util/CronUtils.class b/huacai-quartz/target/classes/com/huacai/quartz/util/CronUtils.class new file mode 100644 index 0000000000000000000000000000000000000000..0f15b0a0956e13a8307e4606ff343c0b6d5b1bf6 GIT binary patch literal 1283 zcmah}T~pIQ6g>-t&{#@a`Nofe6k1T?2MStbhC!SG13FWz;@c9|5=_&YZkg(Fz47=}t-!T4O|p8RSodnI?JR`x`tVwYToxLwI} z$KE+_>r0yrfSmMfw~)9mlTtf~@K2$8E(u$q-7f)fF0x;10&A7$UPle0?2MN6u7hG6{} zij8Vs%+aXKBELq&ibI(@$z!;Lds>(6*e~GgUTF`7#*19kUiOVe@IZ2sScVErN zS+^!y4M(fswlYy3uAj6R2cpxRlid2W&dWh5v7iwvHDQq`YCOlUMr(9|Bva)ujF?wW zI>9mGqvuLh&k`?frDya^^cmGcj3Q0WPBd{Oz(S~VS)iKbG zyR=5?4!DOI+Jz}ZS(<~7p?#;r^T7j6!T0gN4}OS8L9jr(&nSL^t}R0Ee#Z{X=|dzA zd_0}*cZ^&Vn*@;t#tAz`)=9$8_{sYzftvhTJoaPasKY!VZ>r#xMj67G!+ZewjeL~| zsZ&UE`UL%3#~3`qtwW3+m?ubWDGo=N{OzY4z~BWMbChzPtk3AiF8DN(L^F&YJS7|b Vk%&?@Ae3q*^j;BS6s44U{twhuBE0|r literal 0 HcmV?d00001 diff --git a/huacai-quartz/target/classes/com/huacai/quartz/util/JobInvokeUtil.class b/huacai-quartz/target/classes/com/huacai/quartz/util/JobInvokeUtil.class new file mode 100644 index 0000000000000000000000000000000000000000..d5c355948b144ad7b775ddbccab76f46d272e5b8 GIT binary patch literal 5661 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 literal 0 HcmV?d00001 diff --git a/huacai-quartz/target/classes/com/huacai/quartz/util/QuartzDisallowConcurrentExecution.class b/huacai-quartz/target/classes/com/huacai/quartz/util/QuartzDisallowConcurrentExecution.class new file mode 100644 index 0000000000000000000000000000000000000000..ac88e908f2e58771128bdb2319c55265ca4e2979 GIT binary patch literal 889 zcma)5-%k@k5dNmUa#Bwa@E0oDCWaJaycZvgB_;&WXsQXIecx_3bm6)ScY9dw$D|q&iT5&9SUvYozEE;w^zC$W(d#Eo^b(HDcdQgiSESne<6z(hIwQQeV6gvd*d|Q(ZP}ljY_vKy z-TfskpXh8d9@eRmYhTdmZwUyYEYXCYtB^J)}9fgc5Pg$4HBH?HQXnn;N+b?Rsp<>2AS4u0L=}*#wz;pJRkDzHslAQ@|B4fc z13!Qtg_w2HAT^0<$upjr_uhJSR>R!J@N;s2!--*QW2YP zernXve-k7o7NNPwa_2fAti4i^GOr2wdZSy!1FX9!df0$Rc)mi0iSMJa{w%Lql;Ei> z_#n+d_n1}hEb@AKBvj;IeodI%Y;;|05gLoQY|Qz1?THLIqa%XT(mhEibyOrjrlUZ{ zS0Wg4rrgn?7+;EB}RPWQ{qe!yt5Nh{o6N>FgD8JaZ(_(Ku({dt8^mpY9@d8X{9 zDvNtPwJg-I$@j-TIgq_UY)c&Z?1_Eh_y_2FR({CVmH`isGMfQV#x_R|9^(m%Sn@kY v<=Gcbe!~4vwl@__b(mrWs#DNRfZ=$G%CswXu*>LYjB+@$$L|4u=WzHB$_>NO literal 0 HcmV?d00001 diff --git a/huacai-quartz/target/classes/com/huacai/quartz/util/ScheduleUtils.class b/huacai-quartz/target/classes/com/huacai/quartz/util/ScheduleUtils.class new file mode 100644 index 0000000000000000000000000000000000000000..e87d500eba9c19c08caec709cbdaafe465265611 GIT binary patch literal 6371 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 XbNTdf{@t8)-ey0~CQpcp&Hn!ZPTl_0 literal 0 HcmV?d00001 diff --git a/huacai-quartz/target/classes/mapper/quartz/SysJobLogMapper.xml b/huacai-quartz/target/classes/mapper/quartz/SysJobLogMapper.xml new file mode 100644 index 0000000..1345282 --- /dev/null +++ b/huacai-quartz/target/classes/mapper/quartz/SysJobLogMapper.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, create_time + from sys_job_log + + + + + + + + + + delete from sys_job_log where job_log_id = #{jobLogId} + + + + delete from sys_job_log where job_log_id in + + #{jobLogId} + + + + + truncate table sys_job_log + + + + insert into sys_job_log( + job_log_id, + job_name, + job_group, + invoke_target, + job_message, + status, + exception_info, + create_time + )values( + #{jobLogId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{jobMessage}, + #{status}, + #{exceptionInfo}, + sysdate() + ) + + + diff --git a/huacai-quartz/target/classes/mapper/quartz/SysJobMapper.xml b/huacai-quartz/target/classes/mapper/quartz/SysJobMapper.xml new file mode 100644 index 0000000..f161384 --- /dev/null +++ b/huacai-quartz/target/classes/mapper/quartz/SysJobMapper.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark + from sys_job + + + + + + + + + + delete from sys_job where job_id = #{jobId} + + + + delete from sys_job where job_id in + + #{jobId} + + + + + update sys_job + + job_name = #{jobName}, + job_group = #{jobGroup}, + invoke_target = #{invokeTarget}, + cron_expression = #{cronExpression}, + misfire_policy = #{misfirePolicy}, + concurrent = #{concurrent}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where job_id = #{jobId} + + + + insert into sys_job( + job_id, + job_name, + job_group, + invoke_target, + cron_expression, + misfire_policy, + concurrent, + status, + remark, + create_by, + create_time + )values( + #{jobId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{cronExpression}, + #{misfirePolicy}, + #{concurrent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + +