From 7321a9e172145beaaaea06c053510f09848db7d1 Mon Sep 17 00:00:00 2001 From: Odboy Date: Wed, 29 Oct 2025 21:07:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A1=B9=E7=9B=AE=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cutejava-springboot3/.gitignore | 9 + cutejava-springboot3/LICENSE | 191 ++++ .../cutejava-framework/pom.xml | 12 + .../cn/odboy/annotation/AnonymousAccess.java | 29 + .../annotation/AnonymousDeleteMapping.java | 75 ++ .../odboy/annotation/AnonymousGetMapping.java | 75 ++ .../annotation/AnonymousPatchMapping.java | 75 ++ .../annotation/AnonymousPostMapping.java | 75 ++ .../odboy/annotation/AnonymousPutMapping.java | 75 ++ .../src/main/java/cn/odboy/base/CsArgs.java | 44 + .../cn/odboy/base/CsBaseCreateTimeTb.java | 35 + .../main/java/cn/odboy/base/CsBaseTimeTb.java | 39 + .../cn/odboy/base/CsBaseUpdateTimeTb.java | 35 + .../cn/odboy/base/CsBaseUserCreateTimeTb.java | 67 ++ .../cn/odboy/base/CsBaseUserTimeLogicTb.java | 57 ++ .../java/cn/odboy/base/CsBaseUserTimeTb.java | 77 ++ .../src/main/java/cn/odboy/base/CsObject.java | 29 + .../main/java/cn/odboy/base/CsPageArgs.java | 47 + .../main/java/cn/odboy/base/CsPageResult.java | 52 ++ .../java/cn/odboy/base/CsSelectOptionVo.java | 38 + .../cn/odboy/constant/CaptchaCodeEnum.java | 43 + .../java/cn/odboy/constant/FileTypeEnum.java | 34 + .../cn/odboy/constant/RequestMethodEnum.java | 69 ++ .../java/cn/odboy/constant/SystemConst.java | 53 ++ .../context/BeanLoadFinishListener.java | 37 + .../framework/context/BootApplication.java | 54 ++ .../framework/context/CsRequestHolder.java | 34 + .../framework/context/CsScriptHelper.java | 63 ++ .../framework/context/CsSpringBeanHolder.java | 172 ++++ .../cn/odboy/framework/doc/SwaggerConfig.java | 154 ++++ .../exception/BadRequestException.java | 45 + .../framework/exception/ServerException.java | 42 + .../framework/exception/handler/ApiError.java | 46 + .../handler/GlobalExceptionHandler.java | 91 ++ .../logging/ExecutionTimeStatisticsFace.java | 40 + .../ExecutionTimeStatisticsFaceAspect.java | 50 ++ .../controller/HealthCheckController.java | 60 ++ .../service/CsHealthCheckPointService.java | 31 + .../mybatisplus/config/CsMpQTypeEnum.java | 87 ++ .../mybatisplus/config/CsMpQuery.java | 46 + .../mybatisplus/config/DataTypeEnum.java | 159 ++++ .../config/MpCustomP6SpyLogger.java | 66 ++ .../config/MpInsertBatchSqlInjector.java | 45 + .../mybatisplus/config/MybatisPlusConfig.java | 55 ++ .../config/RemoveDruidAdConfig.java | 92 ++ .../mybatisplus/core/CsMpAnyQUtil.java | 86 ++ .../mybatisplus/core/CsMpCmdGenUtil.java | 228 +++++ .../framework/mybatisplus/core/CsMpQUtil.java | 231 +++++ .../core/interfaces/CsMpMapper.java | 189 ++++ .../core/interfaces/CsMpService.java | 74 ++ .../core/interfaces/CsMpServiceImpl.java | 259 ++++++ .../framework/properties/AppProperties.java | 43 + .../properties/model/CaptchaModel.java | 30 + .../model/ContentRsaEncodeSettingModel.java | 30 + .../properties/model/JwtAuthSettingModel.java | 48 + .../properties/model/OSSConfigModel.java | 42 + .../properties/model/StorageOSSModel.java | 30 + .../model/SwaggerApiDocSettingModel.java | 31 + .../model/ThreadPoolSettingModel.java | 48 + .../model/UserLoginCaptchaSettingModel.java | 132 +++ .../model/UserLoginSettingModel.java | 36 + .../framework/quartz/config/QuartzConfig.java | 63 ++ .../framework/quartz/core/QuartzRunnable.java | 57 ++ .../odboy/framework/redis/CsRedisHelper.java | 843 ++++++++++++++++++ .../framework/redis/RedisConfiguration.java | 202 +++++ .../server/config/AsyncTheadPoolConfig.java | 101 +++ .../DateStringArraysToListDateConverter.java | 47 + .../converters/DateStringToDateConverter.java | 42 + .../server/core/ConfigurerAdapter.java | 123 +++ .../server/core/CsFileLocalUploadHelper.java | 53 ++ .../framework/websocket/config/WsConfig.java | 30 + .../framework/websocket/config/WsFactory.java | 43 + .../websocket/context/CsWsClientManager.java | 67 ++ .../websocket/context/CsWsMessage.java | 33 + .../websocket/context/CsWsServer.java | 175 ++++ .../framework/websocket/model/WsSidVo.java | 29 + .../websocket/util/WsMessageUtil.java | 43 + .../java/cn/odboy/util/CsAnonTagUtil.java | 112 +++ .../cn/odboy/util/CsBase64SecretUtil.java | 50 ++ .../java/cn/odboy/util/CsBigDecimalUtil.java | 154 ++++ .../java/cn/odboy/util/CsBrowserUtil.java | 74 ++ .../main/java/cn/odboy/util/CsClassUtil.java | 39 + .../main/java/cn/odboy/util/CsCloseUtil.java | 49 + .../main/java/cn/odboy/util/CsCollUtil.java | 224 +++++ .../main/java/cn/odboy/util/CsDateUtil.java | 150 ++++ .../java/cn/odboy/util/CsDesEncryptUtil.java | 97 ++ .../cn/odboy/util/CsDistributedIdUtil.java | 206 +++++ .../main/java/cn/odboy/util/CsFileUtil.java | 398 +++++++++ .../src/main/java/cn/odboy/util/CsIPUtil.java | 90 ++ .../main/java/cn/odboy/util/CsJsonUtil.java | 50 ++ .../main/java/cn/odboy/util/CsListUtil.java | 73 ++ .../main/java/cn/odboy/util/CsLogFmtUtil.java | 51 ++ .../main/java/cn/odboy/util/CsPageUtil.java | 91 ++ .../cn/odboy/util/CsResourceTemplateUtil.java | 41 + .../odboy/util/CsReturnValueHandleUtil.java | 73 ++ .../java/cn/odboy/util/CsRsaEncryptUtil.java | 214 +++++ .../main/java/cn/odboy/util/CsStringUtil.java | 128 +++ .../main/java/cn/odboy/util/CsSystemUtil.java | 35 + .../main/java/cn/odboy/util/CsValidUtil.java | 54 ++ .../cutejava-module-system/pom.xml | 27 + .../system/constant/SystemCaptchaBizEnum.java | 66 ++ .../system/constant/SystemDataScopeEnum.java | 49 + .../constant/TransferProtocolConst.java | 23 + .../controller/SystemAuthController.java | 156 ++++ .../controller/SystemCaptchaController.java | 58 ++ .../controller/SystemComponentController.java | 49 + .../controller/SystemDeptController.java | 116 +++ .../controller/SystemDictController.java | 98 ++ .../SystemDictDetailController.java | 93 ++ .../controller/SystemEmailController.java | 63 ++ .../controller/SystemJobController.java | 94 ++ .../SystemLocalStorageController.java | 97 ++ .../controller/SystemMenuController.java | 150 ++++ .../controller/SystemMonitorController.java | 44 + .../controller/SystemOnlineController.java | 71 ++ .../SystemOssStorageController.java | 77 ++ .../controller/SystemQuartzJobController.java | 140 +++ .../controller/SystemRoleController.java | 148 +++ .../controller/SystemUserController.java | 237 +++++ .../system/dal/dataobject/SystemDeptTb.java | 102 +++ .../dal/dataobject/SystemDictDetailTb.java | 56 ++ .../system/dal/dataobject/SystemDictTb.java | 45 + .../dal/dataobject/SystemEmailConfigTb.java | 58 ++ .../system/dal/dataobject/SystemJobTb.java | 69 ++ .../dal/dataobject/SystemLocalStorageTb.java | 62 ++ .../system/dal/dataobject/SystemMenuTb.java | 123 +++ .../dal/dataobject/SystemOperationLogTb.java | 101 +++ .../dal/dataobject/SystemOssStorageTb.java | 81 ++ .../dal/dataobject/SystemQuartzJobTb.java | 80 ++ .../dal/dataobject/SystemQuartzLogTb.java | 63 ++ .../dal/dataobject/SystemRoleMenuTb.java | 16 + .../system/dal/dataobject/SystemRoleTb.java | 86 ++ .../system/dal/dataobject/SystemUserTb.java | 116 +++ .../model/SystemCheckEmailCaptchaArgs.java | 34 + .../dal/model/SystemCreateDeptArgs.java | 37 + .../dal/model/SystemCreateDictArgs.java | 31 + .../dal/model/SystemCreateDictDetailArgs.java | 38 + .../system/dal/model/SystemCreateJobArgs.java | 34 + .../dal/model/SystemCreateRoleArgs.java | 43 + .../system/dal/model/SystemMenuMetaVo.java | 38 + .../odboy/system/dal/model/SystemMenuVo.java | 56 ++ .../system/dal/model/SystemOssStorageVo.java | 27 + .../dal/model/SystemProductLineTreeVo.java | 31 + .../system/dal/model/SystemProductLineVo.java | 38 + .../system/dal/model/SystemQueryDeptArgs.java | 45 + .../system/dal/model/SystemQueryDictArgs.java | 33 + .../dal/model/SystemQueryDictDetailArgs.java | 32 + .../system/dal/model/SystemQueryJobArgs.java | 44 + .../system/dal/model/SystemQueryMenuArgs.java | 39 + .../dal/model/SystemQueryQuartzJobArgs.java | 42 + .../system/dal/model/SystemQueryRoleArgs.java | 42 + .../dal/model/SystemQueryStorageArgs.java | 39 + .../system/dal/model/SystemQueryUserArgs.java | 56 ++ .../system/dal/model/SystemRoleCodeVo.java | 35 + .../system/dal/model/SystemSendEmailArgs.java | 47 + .../system/dal/model/SystemSimpleUserVo.java | 58 ++ .../dal/model/SystemUpdateQuartzJobArgs.java | 47 + .../model/SystemUpdateUserPasswordArgs.java | 33 + .../system/dal/model/SystemUserInfoVo.java | 37 + .../system/dal/model/SystemUserJwtVo.java | 82 ++ .../system/dal/model/SystemUserLoginArgs.java | 42 + .../system/dal/model/SystemUserOnlineVo.java | 55 ++ .../system/dal/mysql/SystemDeptMapper.java | 45 + .../dal/mysql/SystemDictDetailMapper.java | 42 + .../system/dal/mysql/SystemDictMapper.java | 39 + .../dal/mysql/SystemEmailConfigMapper.java | 30 + .../system/dal/mysql/SystemJobMapper.java | 37 + .../dal/mysql/SystemLocalStorageMapper.java | 35 + .../system/dal/mysql/SystemMenuMapper.java | 52 ++ .../dal/mysql/SystemOperationLogMapper.java | 25 + .../dal/mysql/SystemOssStorageMapper.java | 41 + .../dal/mysql/SystemQuartzJobMapper.java | 39 + .../dal/mysql/SystemQuartzLogMapper.java | 35 + .../dal/mysql/SystemRoleDeptMapper.java | 35 + .../system/dal/mysql/SystemRoleMapper.java | 50 ++ .../dal/mysql/SystemRoleMenuMapper.java | 39 + .../system/dal/mysql/SystemUserJobMapper.java | 35 + .../system/dal/mysql/SystemUserMapper.java | 59 ++ .../dal/mysql/SystemUserRoleMapper.java | 30 + .../system/dal/redis/SystemCacheKey.java | 80 ++ .../system/dal/redis/SystemRedisKey.java | 64 ++ .../system/dal/redis/SystemUserInfoDAO.java | 61 ++ .../dal/redis/SystemUserOnlineInfoDAO.java | 155 ++++ .../system/framework/bizlock/BizLock.java | 61 ++ .../framework/bizlock/BizLockAspect.java | 92 ++ .../mybatisplus/MpMetaObjectHandler.java | 59 ++ .../framework/operalog/OperationLog.java | 33 + .../operalog/OperationLogAspect.java | 120 +++ .../operalog/annotaions/OperationLog.java | 32 + .../core/SystemOperationLogAspect.java | 122 +++ .../permission/config/AuthorityConfig.java | 42 + .../config/SpringSecurityConfig.java | 113 +++ .../permission/config/TokenConfigurer.java | 41 + .../permission/core/CsSecurityHelper.java | 135 +++ .../core/handler/JwtAccessDeniedHandler.java | 42 + .../handler/JwtAuthenticationEntryPoint.java | 45 + .../permission/core/handler/TokenFilter.java | 87 ++ .../core/handler/TokenProvider.java | 167 ++++ .../core/handler/UserDetailsHandler.java | 70 ++ .../framework/quartz/ActiveJobRegister.java | 48 + .../framework/quartz/ExecutionJobBean.java | 133 +++ .../system/framework/quartz/QuartzManage.java | 173 ++++ .../storage/OssStorageConfiguration.java | 44 + .../storage/local/MultipartConfig.java | 59 ++ .../storage/minio/MinioRepository.java | 363 ++++++++ .../java/cn/odboy/system/job/TestTask.java | 37 + .../system/service/SystemDataService.java | 89 ++ .../system/service/SystemDeptService.java | 469 ++++++++++ .../service/SystemDictDetailService.java | 98 ++ .../system/service/SystemDictService.java | 149 ++++ .../system/service/SystemEmailService.java | 164 ++++ .../system/service/SystemJobService.java | 142 +++ .../service/SystemLocalStorageService.java | 166 ++++ .../system/service/SystemMenuService.java | 405 +++++++++ .../system/service/SystemMonitorService.java | 209 +++++ .../service/SystemOssStorageService.java | 51 ++ .../service/SystemQuartzJobService.java | 283 ++++++ .../system/service/SystemRoleService.java | 264 ++++++ .../system/service/SystemUserService.java | 346 +++++++ .../impl/SystemOssStorageServiceImpl.java | 157 ++++ .../java/cn/odboy/system/util/SqlUtil.java | 188 ++++ .../resources/mapper/SystemDeptMapper.xml | 109 +++ .../mapper/SystemDictDetailMapper.xml | 40 + .../resources/mapper/SystemDictMapper.xml | 18 + .../main/resources/mapper/SystemJobMapper.xml | 26 + .../mapper/SystemLocalStorageMapper.xml | 23 + .../resources/mapper/SystemMenuMapper.xml | 124 +++ .../mapper/SystemOssStorageMapper.xml | 23 + .../mapper/SystemQuartzJobMapper.xml | 23 + .../mapper/SystemQuartzLogMapper.xml | 21 + .../resources/mapper/SystemRoleDeptMapper.xml | 19 + .../resources/mapper/SystemRoleMapper.xml | 143 +++ .../resources/mapper/SystemRoleMenuMapper.xml | 23 + .../resources/mapper/SystemUserJobMapper.xml | 18 + .../resources/mapper/SystemUserMapper.xml | 197 ++++ .../resources/mapper/SystemUserRoleMapper.xml | 19 + .../template/SystemEmailCaptchaTemplate.ftl | 50 ++ .../SystemQuartzJobTaskAlarmTemplate.ftl | 71 ++ .../cutejava-module-task/pom.xml | 21 + .../task/constant/TaskChangeTypeEnum.java | 48 + .../cn/odboy/task/constant/TaskJobKeys.java | 16 + .../odboy/task/constant/TaskStatusEnum.java | 44 + .../task/controller/TaskTestsController.java | 78 ++ .../java/cn/odboy/task/core/TaskJobBean.java | 196 ++++ .../java/cn/odboy/task/core/TaskManage.java | 255 ++++++ .../cn/odboy/task/core/TaskStepExecutor.java | 29 + .../task/core/impl/TaskStepNodeBuildJava.java | 34 + .../core/impl/TaskStepNodeDeployJava.java | 34 + .../task/core/impl/TaskStepNodeInit.java | 39 + .../core/impl/TaskStepNodeMergeBranch.java | 40 + .../dal/dataobject/TaskInstanceDetailTb.java | 113 +++ .../dal/dataobject/TaskInstanceInfoTb.java | 144 +++ .../dataobject/TaskInstanceStepDetailTb.java | 79 ++ .../dal/dataobject/TaskTemplateInfoTb.java | 100 +++ .../task/dal/model/TaskInstanceInfoVo.java | 26 + .../task/dal/model/TaskInstanceNodeVo.java | 65 ++ .../task/dal/model/TaskTemplateInfoVo.java | 32 + .../TaskTemplateNodeOperateButtonVo.java | 50 ++ .../task/dal/model/TaskTemplateNodeVo.java | 68 ++ .../dal/mysql/TaskInstanceDetailMapper.java | 33 + .../dal/mysql/TaskInstanceInfoMapper.java | 33 + .../mysql/TaskInstanceStepDetailMapper.java | 33 + .../dal/mysql/TaskTemplateInfoMapper.java | 47 + .../service/TaskInstanceDetailService.java | 44 + .../task/service/TaskInstanceInfoService.java | 42 + .../TaskInstanceStepDetailService.java | 54 ++ .../task/service/TaskTemplateInfoService.java | 32 + .../impl/TaskInstanceDetailServiceImpl.java | 91 ++ .../impl/TaskInstanceInfoServiceImpl.java | 107 +++ .../TaskInstanceStepDetailServiceImpl.java | 69 ++ .../impl/TaskTemplateInfoServiceImpl.java | 40 + .../mapper/TaskInstanceDetailMapper.xml | 5 + .../mapper/TaskInstanceInfoMapper.xml | 5 + .../mapper/TaskTemplateInfoMapper.xml | 19 + cutejava-springboot3/cutejava-starter/pom.xml | 41 + .../src/main/java/cn/odboy/AppRun.java | 36 + .../src/main/java/cn/odboy/GenCode.java | 51 ++ .../java/cn/odboy/HealthCheckPointer.java | 45 + .../src/main/resources/application-dev.yml | 258 ++++++ .../src/main/resources/application-prod.yml | 258 ++++++ .../src/main/resources/application.yml | 5 + .../src/main/resources/banner.txt | 8 + .../src/main/resources/logback.xml | 43 + .../src/main/resources/spy.properties | 20 + .../java/cn/odboy/MyBatisMapperTests.java | 54 ++ .../java/cn/odboy/SystemApplicationTests.java | 70 ++ .../cn/odboy/util/CsDesEncryptUtilTest.java | 56 ++ .../java/cn/odboy/util/CsFileUtilTest.java | 52 ++ .../java/cn/odboy/util/CsStringUtilTest.java | 60 ++ .../java/cn/odboy/util/WorkTimeTests.java | 30 + cutejava-springboot3/pom.xml | 473 ++++++++++ 291 files changed, 23872 insertions(+) create mode 100644 cutejava-springboot3/.gitignore create mode 100644 cutejava-springboot3/LICENSE create mode 100644 cutejava-springboot3/cutejava-framework/pom.xml create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousAccess.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousDeleteMapping.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousGetMapping.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPatchMapping.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPostMapping.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPutMapping.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsArgs.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseCreateTimeTb.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseTimeTb.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUpdateTimeTb.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUserCreateTimeTb.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUserTimeLogicTb.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUserTimeTb.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsObject.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsPageArgs.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsPageResult.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsSelectOptionVo.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/CaptchaCodeEnum.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/FileTypeEnum.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/RequestMethodEnum.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/SystemConst.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/BeanLoadFinishListener.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/BootApplication.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/CsRequestHolder.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/CsScriptHelper.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/CsSpringBeanHolder.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/doc/SwaggerConfig.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/BadRequestException.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/ServerException.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/ApiError.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/GlobalExceptionHandler.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeStatisticsFace.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeStatisticsFaceAspect.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/monitor/controller/HealthCheckController.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/monitor/service/CsHealthCheckPointService.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/CsMpQTypeEnum.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/CsMpQuery.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/DataTypeEnum.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpCustomP6SpyLogger.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpInsertBatchSqlInjector.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MybatisPlusConfig.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/RemoveDruidAdConfig.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/CsMpAnyQUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/CsMpCmdGenUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/CsMpQUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/CsMpMapper.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/CsMpService.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/CsMpServiceImpl.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/AppProperties.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/CaptchaModel.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ContentRsaEncodeSettingModel.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/JwtAuthSettingModel.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/OSSConfigModel.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/StorageOSSModel.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/SwaggerApiDocSettingModel.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ThreadPoolSettingModel.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginCaptchaSettingModel.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginSettingModel.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/quartz/config/QuartzConfig.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/quartz/core/QuartzRunnable.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/redis/CsRedisHelper.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/redis/RedisConfiguration.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/config/AsyncTheadPoolConfig.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringArraysToListDateConverter.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringToDateConverter.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/core/ConfigurerAdapter.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/core/CsFileLocalUploadHelper.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/config/WsConfig.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/config/WsFactory.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/CsWsClientManager.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/CsWsMessage.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/CsWsServer.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/model/WsSidVo.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/util/WsMessageUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsAnonTagUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsBase64SecretUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsBigDecimalUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsBrowserUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsClassUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsCloseUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsCollUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsDateUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsDesEncryptUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsDistributedIdUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsFileUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsIPUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsJsonUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsListUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsLogFmtUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsPageUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsResourceTemplateUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsReturnValueHandleUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsRsaEncryptUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsStringUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsSystemUtil.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsValidUtil.java create mode 100644 cutejava-springboot3/cutejava-module-system/pom.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemCaptchaBizEnum.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemDataScopeEnum.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/constant/TransferProtocolConst.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemAuthController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemCaptchaController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemComponentController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDeptController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictDetailController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemEmailController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemJobController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemLocalStorageController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMenuController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMonitorController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOnlineController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOssStorageController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemQuartzJobController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemRoleController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemUserController.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDeptTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictDetailTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemEmailConfigTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemJobTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemLocalStorageTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemMenuTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOperationLogTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOssStorageTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzJobTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzLogTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleMenuTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserTb.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCheckEmailCaptchaArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateDeptArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateDictArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateDictDetailArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateJobArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateRoleArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemMenuMetaVo.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemMenuVo.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemOssStorageVo.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemProductLineTreeVo.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemProductLineVo.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryDeptArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryDictArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryDictDetailArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryJobArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryMenuArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryQuartzJobArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryRoleArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryStorageArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryUserArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemRoleCodeVo.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemSendEmailArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemSimpleUserVo.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUpdateQuartzJobArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUpdateUserPasswordArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserInfoVo.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserJwtVo.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserLoginArgs.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserOnlineVo.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDeptMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictDetailMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemEmailConfigMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemJobMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemLocalStorageMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemMenuMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOperationLogMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOssStorageMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzJobMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzLogMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleDeptMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMenuMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserJobMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserRoleMapper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemCacheKey.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemRedisKey.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserInfoDAO.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserOnlineInfoDAO.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLock.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLockAspect.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/mybatisplus/MpMetaObjectHandler.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLog.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLogAspect.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/annotaions/OperationLog.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/core/SystemOperationLogAspect.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/AuthorityConfig.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/SpringSecurityConfig.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/TokenConfigurer.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/CsSecurityHelper.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAccessDeniedHandler.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAuthenticationEntryPoint.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenFilter.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenProvider.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/UserDetailsHandler.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ActiveJobRegister.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ExecutionJobBean.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/QuartzManage.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/OssStorageConfiguration.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/local/MultipartConfig.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/minio/MinioRepository.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/job/TestTask.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDataService.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDeptService.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictDetailService.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictService.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemEmailService.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemJobService.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemLocalStorageService.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMenuService.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMonitorService.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemOssStorageService.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemQuartzJobService.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleService.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserService.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/impl/SystemOssStorageServiceImpl.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/util/SqlUtil.java create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemDeptMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemDictDetailMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemDictMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemJobMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemLocalStorageMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemMenuMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemOssStorageMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemQuartzJobMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemQuartzLogMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemRoleDeptMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemRoleMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemRoleMenuMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemUserJobMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemUserMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemUserRoleMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/template/SystemEmailCaptchaTemplate.ftl create mode 100644 cutejava-springboot3/cutejava-module-system/src/main/resources/template/SystemQuartzJobTaskAlarmTemplate.ftl create mode 100644 cutejava-springboot3/cutejava-module-task/pom.xml create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/constant/TaskChangeTypeEnum.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/constant/TaskJobKeys.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/constant/TaskStatusEnum.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/controller/TaskTestsController.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/TaskJobBean.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/TaskManage.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/TaskStepExecutor.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeBuildJava.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeDeployJava.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeInit.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeMergeBranch.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskInstanceDetailTb.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskInstanceInfoTb.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskInstanceStepDetailTb.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskTemplateInfoTb.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskInstanceInfoVo.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskInstanceNodeVo.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskTemplateInfoVo.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskTemplateNodeOperateButtonVo.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskTemplateNodeVo.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskInstanceDetailMapper.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskInstanceInfoMapper.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskInstanceStepDetailMapper.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskTemplateInfoMapper.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskInstanceDetailService.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskInstanceInfoService.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskInstanceStepDetailService.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskTemplateInfoService.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskInstanceDetailServiceImpl.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskInstanceInfoServiceImpl.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskInstanceStepDetailServiceImpl.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskTemplateInfoServiceImpl.java create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/resources/mapper/TaskInstanceDetailMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/resources/mapper/TaskInstanceInfoMapper.xml create mode 100644 cutejava-springboot3/cutejava-module-task/src/main/resources/mapper/TaskTemplateInfoMapper.xml create mode 100644 cutejava-springboot3/cutejava-starter/pom.xml create mode 100644 cutejava-springboot3/cutejava-starter/src/main/java/cn/odboy/AppRun.java create mode 100644 cutejava-springboot3/cutejava-starter/src/main/java/cn/odboy/GenCode.java create mode 100644 cutejava-springboot3/cutejava-starter/src/main/java/cn/odboy/HealthCheckPointer.java create mode 100644 cutejava-springboot3/cutejava-starter/src/main/resources/application-dev.yml create mode 100644 cutejava-springboot3/cutejava-starter/src/main/resources/application-prod.yml create mode 100644 cutejava-springboot3/cutejava-starter/src/main/resources/application.yml create mode 100644 cutejava-springboot3/cutejava-starter/src/main/resources/banner.txt create mode 100644 cutejava-springboot3/cutejava-starter/src/main/resources/logback.xml create mode 100644 cutejava-springboot3/cutejava-starter/src/main/resources/spy.properties create mode 100644 cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/MyBatisMapperTests.java create mode 100644 cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/SystemApplicationTests.java create mode 100644 cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/CsDesEncryptUtilTest.java create mode 100644 cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/CsFileUtilTest.java create mode 100644 cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/CsStringUtilTest.java create mode 100644 cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/WorkTimeTests.java create mode 100644 cutejava-springboot3/pom.xml diff --git a/cutejava-springboot3/.gitignore b/cutejava-springboot3/.gitignore new file mode 100644 index 00000000..a4dad206 --- /dev/null +++ b/cutejava-springboot3/.gitignore @@ -0,0 +1,9 @@ +### IDEA ### +.DS_Store +~/* +.idea/* +*.iml +*/target/* +*/*.iml +/.gradle/ +/application.pid \ No newline at end of file diff --git a/cutejava-springboot3/LICENSE b/cutejava-springboot3/LICENSE new file mode 100644 index 00000000..78a36cb2 --- /dev/null +++ b/cutejava-springboot3/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "{}" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright 2019 Zheng Jie + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/cutejava-springboot3/cutejava-framework/pom.xml b/cutejava-springboot3/cutejava-framework/pom.xml new file mode 100644 index 00000000..cfdb3b22 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/pom.xml @@ -0,0 +1,12 @@ + + + + cutejava + cn.odboy + 1.4.1 + + 4.0.0 + cutejava-framework + 公共模块 + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousAccess.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousAccess.java new file mode 100644 index 00000000..a941eac2 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousAccess.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.annotation; + +import java.lang.annotation.*; + +/** + * 用于标记匿名访问方法 + */ +@Inherited +@Documented +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AnonymousAccess { + +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousDeleteMapping.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousDeleteMapping.java new file mode 100644 index 00000000..f37cb132 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousDeleteMapping.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.annotation; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.lang.annotation.*; + +/** + * Annotation for mapping HTTP {@code DELETE} requests onto specific handler methods. 支持匿名访问 DeleteMapping + * + * @see AnonymousGetMapping + * @see AnonymousPostMapping + * @see AnonymousPutMapping + * @see AnonymousPatchMapping + * @see RequestMapping + */ +@AnonymousAccess +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.DELETE) +public @interface AnonymousDeleteMapping { + + /** + * Alias for {@link RequestMapping#name}. + */ + @AliasFor(annotation = RequestMapping.class) String name() default ""; + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) String[] params() default {}; + + /** + * Alias for {@link RequestMapping#headers}. + */ + @AliasFor(annotation = RequestMapping.class) String[] headers() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + */ + @AliasFor(annotation = RequestMapping.class) String[] consumes() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + */ + @AliasFor(annotation = RequestMapping.class) String[] produces() default {}; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousGetMapping.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousGetMapping.java new file mode 100644 index 00000000..e72c492b --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousGetMapping.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.annotation; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.lang.annotation.*; + +/** + * Annotation for mapping HTTP {@code GET} requests onto specific handler methods. + *

+ * 支持匿名访问 GetMapping + * + * @see RequestMapping + */ +@AnonymousAccess +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.GET) +public @interface AnonymousGetMapping { + + /** + * Alias for {@link RequestMapping#name}. + */ + @AliasFor(annotation = RequestMapping.class) String name() default ""; + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) String[] params() default {}; + + /** + * Alias for {@link RequestMapping#headers}. + */ + @AliasFor(annotation = RequestMapping.class) String[] headers() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + * + * @since 4.3.5 + */ + @AliasFor(annotation = RequestMapping.class) String[] consumes() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + */ + @AliasFor(annotation = RequestMapping.class) String[] produces() default {}; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPatchMapping.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPatchMapping.java new file mode 100644 index 00000000..4a49586e --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPatchMapping.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.annotation; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.lang.annotation.*; + +/** + * Annotation for mapping HTTP {@code PATCH} requests onto specific handler methods. 支持匿名访问 PatchMapping + * + * @see AnonymousGetMapping + * @see AnonymousPostMapping + * @see AnonymousPutMapping + * @see AnonymousDeleteMapping + * @see RequestMapping + */ +@AnonymousAccess +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.PATCH) +public @interface AnonymousPatchMapping { + + /** + * Alias for {@link RequestMapping#name}. + */ + @AliasFor(annotation = RequestMapping.class) String name() default ""; + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) String[] params() default {}; + + /** + * Alias for {@link RequestMapping#headers}. + */ + @AliasFor(annotation = RequestMapping.class) String[] headers() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + */ + @AliasFor(annotation = RequestMapping.class) String[] consumes() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + */ + @AliasFor(annotation = RequestMapping.class) String[] produces() default {}; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPostMapping.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPostMapping.java new file mode 100644 index 00000000..aeaa028f --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPostMapping.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.annotation; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.lang.annotation.*; + +/** + * Annotation for mapping HTTP {@code POST} requests onto specific handler methods. 支持匿名访问 PostMapping + * + * @see AnonymousGetMapping + * @see AnonymousPostMapping + * @see AnonymousPutMapping + * @see AnonymousDeleteMapping + * @see RequestMapping + */ +@AnonymousAccess +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.POST) +public @interface AnonymousPostMapping { + + /** + * Alias for {@link RequestMapping#name}. + */ + @AliasFor(annotation = RequestMapping.class) String name() default ""; + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) String[] params() default {}; + + /** + * Alias for {@link RequestMapping#headers}. + */ + @AliasFor(annotation = RequestMapping.class) String[] headers() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + */ + @AliasFor(annotation = RequestMapping.class) String[] consumes() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + */ + @AliasFor(annotation = RequestMapping.class) String[] produces() default {}; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPutMapping.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPutMapping.java new file mode 100644 index 00000000..5f54ef12 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPutMapping.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.annotation; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.lang.annotation.*; + +/** + * Annotation for mapping HTTP {@code PUT} requests onto specific handler methods. 支持匿名访问 PutMapping + * + * @see AnonymousGetMapping + * @see AnonymousPostMapping + * @see AnonymousPutMapping + * @see AnonymousDeleteMapping + * @see RequestMapping + */ +@AnonymousAccess +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.PUT) +public @interface AnonymousPutMapping { + + /** + * Alias for {@link RequestMapping#name}. + */ + @AliasFor(annotation = RequestMapping.class) String name() default ""; + + /** + * Alias for {@link RequestMapping#value}. + */ + @AliasFor(annotation = RequestMapping.class) String[] value() default {}; + + /** + * Alias for {@link RequestMapping#path}. + */ + @AliasFor(annotation = RequestMapping.class) String[] path() default {}; + + /** + * Alias for {@link RequestMapping#params}. + */ + @AliasFor(annotation = RequestMapping.class) String[] params() default {}; + + /** + * Alias for {@link RequestMapping#headers}. + */ + @AliasFor(annotation = RequestMapping.class) String[] headers() default {}; + + /** + * Alias for {@link RequestMapping#consumes}. + */ + @AliasFor(annotation = RequestMapping.class) String[] consumes() default {}; + + /** + * Alias for {@link RequestMapping#produces}. + */ + @AliasFor(annotation = RequestMapping.class) String[] produces() default {}; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsArgs.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsArgs.java new file mode 100644 index 00000000..1ab9f522 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsArgs.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.base; + +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotNull; + +/** + * 通用参数 + * + * @author odboy + * @date 2025-07-23 + */ +public class CsArgs { + @Getter + @Setter + public static class FindByLongId extends CsObject { + @NotNull(message = "id必填") + private Long id; + } + + @Getter + @Setter + public static class DeleteById extends CsObject { + @NotNull(message = "id必填") + private Long id; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseCreateTimeTb.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseCreateTimeTb.java new file mode 100644 index 00000000..03c6fc13 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseCreateTimeTb.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.base; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.annotations.ApiModelProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +public class CsBaseCreateTimeTb extends CsObject { + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true) + private Date createTime; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseTimeTb.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseTimeTb.java new file mode 100644 index 00000000..7412d7bc --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseTimeTb.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.base; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.annotations.ApiModelProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +public class CsBaseTimeTb extends CsObject { + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true) + private Date createTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @ApiModelProperty(value = "更新时间: yyyy-MM-dd HH:mm:ss", hidden = true) + private Date updateTime; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUpdateTimeTb.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUpdateTimeTb.java new file mode 100644 index 00000000..4ed12ff3 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUpdateTimeTb.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.base; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.annotations.ApiModelProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +public class CsBaseUpdateTimeTb extends CsObject { + @TableField(fill = FieldFill.INSERT_UPDATE) + @ApiModelProperty(value = "更新时间: yyyy-MM-dd HH:mm:ss", hidden = true) + private Date updateTime; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUserCreateTimeTb.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUserCreateTimeTb.java new file mode 100644 index 00000000..31da284f --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUserCreateTimeTb.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.base; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.annotations.ApiModelProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.springframework.data.annotation.CreatedBy; + +import java.lang.reflect.Field; +import java.util.Date; + +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +public class CsBaseUserCreateTimeTb extends CsObject { + + @CreatedBy + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建人", hidden = true) + private String createBy; + + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true) + private Date createTime; + + /* 分组校验 */ + public @interface Create { + } + + /* 分组校验 */ + public @interface Update { + } + + @Override + public String toString() { + ToStringBuilder builder = new ToStringBuilder(this); + Field[] fields = this.getClass().getDeclaredFields(); + try { + for (Field f : fields) { + f.setAccessible(true); + builder.append(f.getName(), f.get(this)).append("\n"); + } + } catch (Exception e) { + builder.append("toString builder encounter an error"); + } + return builder.toString(); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUserTimeLogicTb.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUserTimeLogicTb.java new file mode 100644 index 00000000..4654092e --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUserTimeLogicTb.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.base; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableLogic; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.LastModifiedBy; + +import java.util.Date; + +/** + * 逻辑删除通用模型 + * + * @author odboy + * @date 2025-01-12 + */ +@Getter +@Setter +public class CsBaseUserTimeLogicTb extends CsObject { + @CreatedBy + @TableField(value = "create_by", fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建人", hidden = true) + private String createBy; + @LastModifiedBy + @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE) + @ApiModelProperty(value = "更新人", hidden = true) + private String updateBy; + @TableField(value = "create_time", fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建时间", hidden = true) + private Date createTime; + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + @ApiModelProperty(value = "更新时间", hidden = true) + private Date updateTime; + @ApiModelProperty(value = "数据有效性", hidden = true) + @TableLogic + @TableField("available") + private Integer available; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUserTimeTb.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUserTimeTb.java new file mode 100644 index 00000000..0d15c48d --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsBaseUserTimeTb.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.base; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.annotations.ApiModelProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.LastModifiedBy; + +import java.lang.reflect.Field; +import java.util.Date; + +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +public class CsBaseUserTimeTb extends CsObject { + + @CreatedBy + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建人", hidden = true) + private String createBy; + + @LastModifiedBy + @TableField(fill = FieldFill.INSERT_UPDATE) + @ApiModelProperty(value = "更新人", hidden = true) + private String updateBy; + + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true) + private Date createTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @ApiModelProperty(value = "更新时间: yyyy-MM-dd HH:mm:ss", hidden = true) + private Date updateTime; + + /* 分组校验 */ + public @interface Create { + } + + /* 分组校验 */ + public @interface Update { + } + + @Override + public String toString() { + ToStringBuilder builder = new ToStringBuilder(this); + Field[] fields = this.getClass().getDeclaredFields(); + try { + for (Field f : fields) { + f.setAccessible(true); + builder.append(f.getName(), f.get(this)).append("\n"); + } + } catch (Exception e) { + builder.append("toString builder encounter an error"); + } + return builder.toString(); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsObject.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsObject.java new file mode 100644 index 00000000..1854f4f5 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsObject.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.base; + +import java.io.Serializable; + +/** + * 基础对象 + * + * @author odboy + * @date 2022-01-12 + */ +public class CsObject implements Serializable { + private static final long serialVersionUID = 1L; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsPageArgs.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsPageArgs.java new file mode 100644 index 00000000..b209e552 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsPageArgs.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.base; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import java.io.Serializable; + +/** + * 数据分页参数 + * + * @author odboy + * @date 2025-01-20 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CsPageArgs implements Serializable { + @NotNull(message = "参数page不能为空") + @Min(value = 1, message = "参数page最小值为1") + @ApiModelProperty(value = "页码", example = "1") + private Integer page; + @NotNull(message = "参数size不能为空") + @Min(value = 1, message = "参数size最小值为1") + @ApiModelProperty(value = "每页数据量", example = "10") + private Integer size; + private T args; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsPageResult.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsPageResult.java new file mode 100644 index 00000000..7290b44b --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsPageResult.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.base; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * 结果封装类 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class CsPageResult extends CsObject { + private List content; + private long totalElements; + + public static CsPageResult emptyListData() { + CsPageResult result = new CsPageResult<>(); + result.setTotalElements(0); + result.setContent(new ArrayList<>()); + return result; + } + + public static CsPageResult listData(IPage page) { + CsPageResult result = new CsPageResult<>(); + result.setTotalElements(page.getTotal()); + result.setContent(page.getRecords()); + return result; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsSelectOptionVo.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsSelectOptionVo.java new file mode 100644 index 00000000..92d168e2 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/base/CsSelectOptionVo.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.base; + +import lombok.*; + +import java.util.Map; + +/** + * 元数据选项 + * + * @author odboy + * @date 2025-01-13 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class CsSelectOptionVo extends CsObject { + private String label; + private String value; + private Map ext; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/CaptchaCodeEnum.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/CaptchaCodeEnum.java new file mode 100644 index 00000000..9077be53 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/CaptchaCodeEnum.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.constant; + +/** + * 验证码配置枚举 + */ +public enum CaptchaCodeEnum { + /** + * 算数 + */ + ARITHMETIC, + /** + * 中文 + */ + CHINESE, + /** + * 中文闪图 + */ + CHINESE_GIF, + /** + * 闪图 + */ + GIF, + /** + * 静态 + */ + SPEC +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/FileTypeEnum.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/FileTypeEnum.java new file mode 100644 index 00000000..9f83eb5c --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/FileTypeEnum.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum FileTypeEnum { + /* 文件类型 */ + IMAGE("image", "图片"), + DOC("doc", "文档"), + VOICE("voice", "音频"), + VIDEO("video", "视频"), + OTHER("other", "其他"); + + private final String code; + private final String description; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/RequestMethodEnum.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/RequestMethodEnum.java new file mode 100644 index 00000000..87672598 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/RequestMethodEnum.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum RequestMethodEnum { + + /** + * 搜寻 @AnonymousGetMapping + */ + GET("GET"), + + /** + * 搜寻 @AnonymousPostMapping + */ + POST("POST"), + + /** + * 搜寻 @AnonymousPutMapping + */ + PUT("PUT"), + + /** + * 搜寻 @AnonymousPatchMapping + */ + PATCH("PATCH"), + + /** + * 搜寻 @AnonymousDeleteMapping + */ + DELETE("DELETE"), + + /** + * 否则就是所有 Request 接口都放行 + */ + ALL("All"); + + /** + * Request 类型 + */ + private final String type; + + public static RequestMethodEnum find(String type) { + for (RequestMethodEnum value : RequestMethodEnum.values()) { + if (value.getType().equals(type)) { + return value; + } + } + return ALL; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/SystemConst.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/SystemConst.java new file mode 100644 index 00000000..b24d16f7 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constant/SystemConst.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.constant; + +/** + * 常用静态常量 + */ +public interface SystemConst { + /** + * win 系统 + */ + String WIN = "win"; + + /** + * mac 系统 + */ + String MAC = "mac"; + + /** + * 请求头名称 + */ + String HEADER_NAME = "Authorization"; + + /** + * Token前缀 + */ + String TOKEN_PREFIX = "Bearer"; + + /** + * 小数点 + */ + String SYMBOL_DOT = "."; + String SYMBOL_EQUAL = "="; + String SYMBOL_ADD = "+"; + String SYMBOL_SUBTRACT = "-"; + String SYMBOL_AT = "@"; + String PROPERTY_OS_NAME = "os.name"; + String OS_NAME_WINDOWS = "Windows"; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/BeanLoadFinishListener.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/BeanLoadFinishListener.java new file mode 100644 index 00000000..9769cda4 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/BeanLoadFinishListener.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.context; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +/** + * Bean加载完毕 监听 + * + * @author odboy + * @date 2025-01-16 + */ +@Slf4j +@Component +public class BeanLoadFinishListener implements ApplicationListener { + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + log.info("所有的Bean都加载完毕了"); + } +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/BootApplication.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/BootApplication.java new file mode 100644 index 00000000..8d75b9c3 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/BootApplication.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.context; + +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * 主要目的是输出应用的服务地址 + * + * @author odboy + * @date 2024-04-20 + */ +@Slf4j +public class BootApplication { + @Bean + public CsSpringBeanHolder springBeanHolder() { + return new CsSpringBeanHolder(); + } + + protected static void inited(ConfigurableApplicationContext application) throws UnknownHostException { + Environment env = application.getEnvironment(); + String ip = InetAddress.getLocalHost().getHostAddress(); + String port = env.getProperty("server.port"); + String path = StrUtil.isEmpty(env.getProperty("config.servlet.context-path")) ? + "" : + env.getProperty("config.servlet.context-path"); + log.info( + "\n----------------------------------------------------------\n\t" + + "Application is running! Access URLs:\n\t" + "Local: \t\thttp://localhost:" + port + path + "/\n\t" + + "External: \thttp://" + ip + ":" + port + path + "/\n\t" + "Swagger文档: \thttp://" + ip + ":" + port + + path + "/doc.html\n" + "----------------------------------------------------------"); + } +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/CsRequestHolder.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/CsRequestHolder.java new file mode 100644 index 00000000..e13fd661 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/CsRequestHolder.java @@ -0,0 +1,34 @@ + +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.context; + +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.Objects; + +/** + * 获取 HttpServletRequest + */ +public class CsRequestHolder { + + public static HttpServletRequest getHttpServletRequest() { + return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/CsScriptHelper.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/CsScriptHelper.java new file mode 100644 index 00000000..5c5fc9d6 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/CsScriptHelper.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.context; + +import lombok.extern.slf4j.Slf4j; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * 脚本工具 + * + * @author odboy + * @date 2025-09-23 + */ +@Slf4j +@Component +public class CsScriptHelper { + public Object evalObj(String script, Map args) { + try (Context rhino = Context.enter()) { + Scriptable scope = rhino.initStandardObjects(); + bindArgumentsToScope(scope, args); + Object result = rhino.evaluateString(scope, script, "JavaScript", 1, null); + log.info("执行结果: {}", Context.toString(result)); + return result; + } + } + + private void bindArgumentsToScope(Scriptable scope, Map args) { + if (args == null) { + return; + } + for (Map.Entry entry : args.entrySet()) { + Object jsValue = Context.javaToJS(entry.getValue(), scope); + scope.put(entry.getKey().toString(), scope, jsValue); + } + } + + private void bindArgumentsToScope(Scriptable scope, Object args) { + if (args == null) { + return; + } + // 单个对象参数 + Object jsValue = Context.javaToJS(args, scope); + scope.put("args", scope, jsValue); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/CsSpringBeanHolder.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/CsSpringBeanHolder.java new file mode 100644 index 00000000..c6ae78bb --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/context/CsSpringBeanHolder.java @@ -0,0 +1,172 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.context; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Slf4j +@SuppressWarnings({"unchecked", "all"}) +public class CsSpringBeanHolder implements ApplicationContextAware, DisposableBean { + + private static ApplicationContext applicationContext = null; + private static final List CALL_BACKS = new ArrayList<>(); + private static boolean addCallback = true; + + /** + * 针对 某些初始化方法, 在SpringContextHolder 未初始化时 提交回调方法。 在SpringContextHolder 初始化后, 进行回调使用 + * + * @param callBack 回调函数 + */ + public synchronized static void addCallBacks(CallBack callBack) { + if (addCallback) { + CsSpringBeanHolder.CALL_BACKS.add(callBack); + } else { + log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName()); + callBack.executor(); + } + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + public static T getBean(String name) { + assertContextInjected(); + return (T) applicationContext.getBean(name); + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + public static T getBean(Class requiredType) { + assertContextInjected(); + return applicationContext.getBean(requiredType); + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @param defaultValue 默认值 + * @param requiredType 返回类型 + * @return / + */ + public static T getProperties(String property, T defaultValue, Class requiredType) { + T result = defaultValue; + try { + result = getBean(Environment.class).getProperty(property, requiredType); + } catch (Exception ignored) { + } + return result; + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @return / + */ + public static String getProperties(String property) { + return getProperties(property, null, String.class); + } + + /** + * 获取SpringBoot 配置信息 + * + * @param property 属性key + * @param requiredType 返回类型 + * @return / + */ + public static T getProperties(String property, Class requiredType) { + return getProperties(property, null, requiredType); + } + + /** + * 检查ApplicationContext不为空. + */ + private static void assertContextInjected() { + if (applicationContext == null) { + throw new IllegalStateException( + "applicaitonContext属性未注入, 请在applicationContext" + + ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder."); + } + } + + /** + * 清除SpringContextHolder中的ApplicationContext为Null. + */ + private static void clearHolder() { + log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext); + applicationContext = null; + } + + @Override + public void destroy() { + CsSpringBeanHolder.clearHolder(); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + if (CsSpringBeanHolder.applicationContext != null) { + log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + + CsSpringBeanHolder.applicationContext); + } + CsSpringBeanHolder.applicationContext = applicationContext; + if (addCallback) { + for (CallBack callBack : CsSpringBeanHolder.CALL_BACKS) { + callBack.executor(); + } + CALL_BACKS.clear(); + } + CsSpringBeanHolder.addCallback = false; + } + + /** + * 获取 @Service 的所有 bean 名称 + * + * @return / + */ + public static List getAllServiceBeanName() { + return new ArrayList<>(Arrays.asList(applicationContext.getBeanNamesForAnnotation(Service.class))); + } + + interface CallBack { + + /** + * 回调执行方法 + */ + void executor(); + + /** + * 本回调任务名称 + * + * @return / + */ + default String getCallBackName() { + return Thread.currentThread().getId() + ":" + this.getClass().getName(); + } + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/doc/SwaggerConfig.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/doc/SwaggerConfig.java new file mode 100644 index 00000000..1c79666c --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/doc/SwaggerConfig.java @@ -0,0 +1,154 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.doc; + +import cn.odboy.constant.SystemConst; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.util.CsAnonTagUtil; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.service.*; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider; +import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * api页面 /doc.html + */ +@Configuration +@EnableSwagger2 +public class SwaggerConfig { + @Value("${server.servlet.context-path:}") + private String apiPath; + @Autowired + private AppProperties properties; + @Autowired + private ApplicationContext applicationContext; + + @Bean + public Docket createRestApi() { + return new Docket(DocumentationType.SWAGGER_2).enable(properties.getSwagger().getEnabled()) + .pathMapping("/") + .apiInfo(apiInfo()) + .select() + .paths(PathSelectors.regex("^(?!/error).*")) + .paths(PathSelectors.any()) + .build() + //添加登陆认证 + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder().description("一个简单且易上手的自动化运维平台") + .title("CuteJava 接口文档") + .version("1.4.1") + .build(); + } + + private List securitySchemes() { + //设置请求头信息 + List securitySchemes = new ArrayList<>(); + ApiKey apiKey = new ApiKey(SystemConst.HEADER_NAME, SystemConst.HEADER_NAME, "header"); + securitySchemes.add(apiKey); + return securitySchemes; + } + + private List securityContexts() { + //设置需要登录认证的路径 + List securityContexts = new ArrayList<>(); + securityContexts.add(getContextByPath()); + return securityContexts; + } + + private SecurityContext getContextByPath() { + Set urls = CsAnonTagUtil.getAllAnonymousUrl(applicationContext); + urls = urls.stream().filter(url -> !"/".equals(url)).collect(Collectors.toSet()); + String regExp = "^(?!" + apiPath + String.join("|" + apiPath, urls) + ").*$"; + return SecurityContext.builder().securityReferences(defaultAuth()).operationSelector(o -> o.requestMappingPattern() + // 排除不需要认证的接口 + .matches(regExp)).build(); + } + + private List defaultAuth() { + List securityReferences = new ArrayList<>(); + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + securityReferences.add(new SecurityReference(SystemConst.HEADER_NAME, authorizationScopes)); + return securityReferences; + } + + /** + * 解决Springfox与SpringBoot集成后, WebMvcRequestHandlerProvider和WebFluxRequestHandlerProvider冲突问题 + * + * @return / + */ + @Bean + @SuppressWarnings({"unchecked", "all"}) + public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() { + return new BeanPostProcessor() { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) { + customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); + } + return bean; + } + + private void customizeSpringfoxHandlerMappings(List mappings) { + List filteredMappings = + mappings.stream().filter(mapping -> mapping.getPatternParser() == null).collect(Collectors.toList()); + mappings.clear(); + mappings.addAll(filteredMappings); + } + + private List getHandlerMappings(Object bean) { + Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); + if (field != null) { + field.setAccessible(true); + try { + return (List) field.get(bean); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Failed to access handlerMappings field", e); + } + } + return Collections.emptyList(); + } + }; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/BadRequestException.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/BadRequestException.java new file mode 100644 index 00000000..9fa1d8d7 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/BadRequestException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +/** + * 统一异常处理 + */ +@Getter +public class BadRequestException extends RuntimeException { + + private Integer status = BAD_REQUEST.value(); + + public BadRequestException(String msg) { + super(msg); + } + + public BadRequestException(HttpStatus status, String msg) { + super(msg); + this.status = status.value(); + } + + public BadRequestException(Throwable cause) { + super(cause.getMessage()); + this.status = BAD_REQUEST.value(); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/ServerException.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/ServerException.java new file mode 100644 index 00000000..3e061b6f --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/ServerException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; + +@Getter +public class ServerException extends RuntimeException { + + private Integer status = INTERNAL_SERVER_ERROR.value(); + + public ServerException(String msg) { + super(msg); + } + + public ServerException(HttpStatus status, String msg) { + super(msg); + this.status = status.value(); + } + + public ServerException(Throwable cause) { + super(cause.getMessage()); + this.status = INTERNAL_SERVER_ERROR.value(); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/ApiError.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/ApiError.java new file mode 100644 index 00000000..c941a029 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/ApiError.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.exception.handler; + +import lombok.Data; + +@Data +public final class ApiError { + + private Integer status = 400; + private Long timestamp; + private String message; + + private ApiError() { + timestamp = System.currentTimeMillis(); + } + + public static ApiError error(final String message) { + ApiError apiError = new ApiError(); + apiError.setMessage(message); + return apiError; + } + + public static ApiError error(final Integer status, final String message) { + ApiError apiError = new ApiError(); + apiError.setStatus(status); + apiError.setMessage(message); + return apiError; + } +} + + diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/GlobalExceptionHandler.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/GlobalExceptionHandler.java new file mode 100644 index 00000000..15e26ece --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,91 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.exception.handler; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.odboy.framework.exception.BadRequestException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +/** + * 全局异常处理 + * + * @author odboy + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * 处理所有不可知的异常 + */ + @ExceptionHandler(Throwable.class) + public ResponseEntity handleException(Throwable e) { + // 打印堆栈信息 + log.error(ExceptionUtil.stacktraceToString(e)); + return buildResponseEntity(ApiError.error(e.getMessage())); + } + + /** + * BadCredentialsException + */ + @ExceptionHandler(BadCredentialsException.class) + public ResponseEntity badCredentialsException(BadCredentialsException e) { + // 打印堆栈信息 + String message = "坏的凭证".equals(e.getMessage()) ? "用户名或密码不正确" : e.getMessage(); + log.error(message); + return buildResponseEntity(ApiError.error(message)); + } + + /** + * 处理自定义异常 + */ + @ExceptionHandler(value = BadRequestException.class) + public ResponseEntity badRequestException(BadRequestException e) { + // 打印堆栈信息 + log.error(ExceptionUtil.stacktraceToString(e)); + return buildResponseEntity(ApiError.error(e.getStatus(), e.getMessage())); + } + + /** + * 处理所有接口数据验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + // 打印堆栈信息 + log.error(ExceptionUtil.stacktraceToString(e)); + ObjectError objectError = e.getBindingResult().getAllErrors().get(0); + String message = objectError.getDefaultMessage(); + if (objectError instanceof FieldError) { + message = ((FieldError) objectError).getField() + ": " + message; + } + return buildResponseEntity(ApiError.error(message)); + } + + /** + * 统一返回 + */ + private ResponseEntity buildResponseEntity(ApiError apiError) { + return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getStatus())); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeStatisticsFace.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeStatisticsFace.java new file mode 100644 index 00000000..c1f67785 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeStatisticsFace.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.logging; + +import java.lang.annotation.*; + +/** + * 统计执行时长 + * + * @author odboy + */ +@Inherited +@Documented +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExecutionTimeStatisticsFace { + /** + * 接口描述 + */ + String description(); + + /** + * 是否展示异常日志 + */ + boolean showErrLog() default true; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeStatisticsFaceAspect.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeStatisticsFaceAspect.java new file mode 100644 index 00000000..bcd41cda --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeStatisticsFaceAspect.java @@ -0,0 +1,50 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.logging; + +import cn.hutool.core.date.TimeInterval; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +/** + * 执行时间统计 + * + * @author odboy + * @date 2025-05-07 + */ +@Slf4j +@Aspect +@Component +public class ExecutionTimeStatisticsFaceAspect { + @Around("@annotation(executionTimeStatisticsFace)") + public Object aroundFace(ProceedingJoinPoint joinPoint, ExecutionTimeStatisticsFace executionTimeStatisticsFace) throws Throwable { + TimeInterval timeInterval = new TimeInterval(); + try { + Object proceed = joinPoint.proceed(); + log.info("[{}] 接口执行成功,耗时: {} ms", executionTimeStatisticsFace.description(), timeInterval.intervalMs()); + return proceed; + } catch (Exception e) { + if (executionTimeStatisticsFace.showErrLog()) { + log.error("[{}] 接口执行失败,耗时: {} ms", executionTimeStatisticsFace.description(), timeInterval.intervalMs(), e); + } + throw e; + } + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/monitor/controller/HealthCheckController.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/monitor/controller/HealthCheckController.java new file mode 100644 index 00000000..05452d20 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/monitor/controller/HealthCheckController.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.monitor.controller; + +import cn.odboy.annotation.AnonymousGetMapping; +import cn.odboy.framework.monitor.service.CsHealthCheckPointService; +import io.swagger.annotations.Api; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 健康检查接口 + */ +@RestController +@RequestMapping("/healthCheck") +@Api(tags = "系统:健康检查接口") +public class HealthCheckController { + @Autowired + private CsHealthCheckPointService healthCheckPointService; + + /** + * 就绪检查 + */ + @AnonymousGetMapping(value = "/readiness") + public ResponseEntity doReadiness() { + return healthCheckPointService.doReadiness(); + } + + /** + * 存活检查 + */ + @AnonymousGetMapping(value = "/liveness") + public ResponseEntity doLiveness() { + return healthCheckPointService.doLiveness(); + } + + // /** + // * 访问首页提示 + // */ + // @AnonymousGetMapping("/") + // public String index() { + // return "success"; + // } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/monitor/service/CsHealthCheckPointService.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/monitor/service/CsHealthCheckPointService.java new file mode 100644 index 00000000..8e9ef84e --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/monitor/service/CsHealthCheckPointService.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.monitor.service; + +import org.springframework.http.ResponseEntity; + +public interface CsHealthCheckPointService { + /** + * 就绪检查 + */ + ResponseEntity doReadiness(); + + /** + * 存活检查 + */ + ResponseEntity doLiveness(); +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/CsMpQTypeEnum.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/CsMpQTypeEnum.java new file mode 100644 index 00000000..58dd26ea --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/CsMpQTypeEnum.java @@ -0,0 +1,87 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.mybatisplus.config; + +/** + * 动态查询 枚举 + */ +public enum CsMpQTypeEnum { + /** + * 相等 + */ + EQUAL + /** + * 大于等于 + */ + , + GREATER_THAN + /** + * 小于等于 + */ + , + LESS_THAN + /** + * 中模糊查询 + */ + , + INNER_LIKE + /** + * 左模糊查询 + */ + , + LEFT_LIKE + /** + * 右模糊查询 + */ + , + RIGHT_LIKE + /** + * 小于 + */ + , + LESS_THAN_NQ + /** + * 包含 + */ + , + IN + /** + * 不包含 + */ + , + NOT_IN + /** + * 不等于 + */ + , + NOT_EQUAL + /** + * between(注:被注解的属性一定得是数组或者集合, 长度大于等于2) + */ + , + BETWEEN + /** + * 不为空 + */ + , + NOT_NULL + /** + * 为空 + */ + , + IS_NULL +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/CsMpQuery.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/CsMpQuery.java new file mode 100644 index 00000000..b04428b6 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/CsMpQuery.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.mybatisplus.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 查询描述 + * + * @date 2023-11-25 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface CsMpQuery { + /** + * 基本对象的属性名 + */ + String propName() default ""; + + /** + * 查询方式 + */ + CsMpQTypeEnum type() default CsMpQTypeEnum.EQUAL; + + /** + * 多字段模糊搜索, 仅支持String类型字段, 多个用逗号隔开, 如@MpQuery(blurry = "email,username") + */ + String blurry() default ""; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/DataTypeEnum.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/DataTypeEnum.java new file mode 100644 index 00000000..3dd71287 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/DataTypeEnum.java @@ -0,0 +1,159 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.mybatisplus.config; + +import cn.odboy.framework.exception.BadRequestException; +import lombok.extern.slf4j.Slf4j; + +/** + * @author / + */ +@Slf4j +@SuppressWarnings({"unchecked", "all"}) +public enum DataTypeEnum { + /** + * mysql + */ + MYSQL("mysql", "mysql", "com.mysql.jdbc.Driver", "`", "`", "'", "'"), + /** + * oracle + */ + ORACLE("oracle", "oracle", "oracle.jdbc.driver.OracleDriver", "\"", "\"", "\"", "\""), + /** + * sql config + */ + SQLSERVER("sqlserver", "sqlserver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "\"", "\"", "\"", "\""), + /** + * h2 + */ + H2("h2", "h2", "org.h2.Driver", "`", "`", "\"", "\""), + /** + * phoenix + */ + PHOENIX("phoenix", "hbase phoenix", "org.apache.phoenix.jdbc.PhoenixDriver", "", "", "\"", "\""), + /** + * mongo + */ + MONGODB("mongo", "mongodb", "mongodb.jdbc.MongoDriver", "`", "`", "\"", "\""), + /** + * sql4es + */ + ELASTICSEARCH("sql4es", "elasticsearch", "nl.anchormen.sql4es.jdbc.ESDriver", "", "", "'", "'"), + /** + * presto + */ + PRESTO("presto", "presto", "com.facebook.presto.jdbc.PrestoDriver", "", "", "\"", "\""), + /** + * moonbox + */ + MOONBOX("moonbox", "moonbox", "moonbox.jdbc.MbDriver", "`", "`", "`", "`"), + /** + * cassandra + */ + CASSANDRA("cassandra", "cassandra", "com.github.adejanovski.cassandra.jdbc.CassandraDriver", "", "", "'", "'"), + /** + * click house + */ + CLICKHOUSE("clickhouse", "clickhouse", "ru.yandex.clickhouse.ClickHouseDriver", "", "", "\"", "\""), + /** + * kylin + */ + KYLIN("kylin", "kylin", "org.apache.kylin.jdbc.Driver", "\"", "\"", "\"", "\""), + /** + * vertica + */ + VERTICA("vertica", "vertica", "com.vertica.jdbc.Driver", "", "", "'", "'"), + /** + * sap + */ + HANA("sap", "sap hana", "com.sap.db.jdbc.Driver", "", "", "'", "'"), + /** + * impala + */ + IMPALA("impala", "impala", "com.cloudera.impala.jdbc41.Driver", "", "", "'", "'"); + private String feature; + private String desc; + private String driver; + private String keywordPrefix; + private String keywordSuffix; + private String aliasPrefix; + private String aliasSuffix; + private static final String JDBC_URL_PREFIX = "jdbc:"; + + DataTypeEnum( + String feature, + String desc, + String driver, + String keywordPrefix, + String keywordSuffix, + String aliasPrefix, + String aliasSuffix) { + this.feature = feature; + this.desc = desc; + this.driver = driver; + this.keywordPrefix = keywordPrefix; + this.keywordSuffix = keywordSuffix; + this.aliasPrefix = aliasPrefix; + this.aliasSuffix = aliasSuffix; + } + + public static DataTypeEnum urlOf(String jdbcUrl) { + String url = jdbcUrl.toLowerCase().trim(); + for (DataTypeEnum dataTypeEnum : values()) { + if (url.startsWith(JDBC_URL_PREFIX + dataTypeEnum.feature)) { + try { + Class aClass = Class.forName(dataTypeEnum.getDriver()); + if (null == aClass) { + throw new BadRequestException("Unable to get driver instance for jdbcUrl: " + jdbcUrl); + } + } catch (ClassNotFoundException e) { + throw new BadRequestException("Unable to get driver instance: " + jdbcUrl); + } + return dataTypeEnum; + } + } + return null; + } + + public String getFeature() { + return feature; + } + + public String getDesc() { + return desc; + } + + public String getDriver() { + return driver; + } + + public String getKeywordPrefix() { + return keywordPrefix; + } + + public String getKeywordSuffix() { + return keywordSuffix; + } + + public String getAliasPrefix() { + return aliasPrefix; + } + + public String getAliasSuffix() { + return aliasSuffix; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpCustomP6SpyLogger.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpCustomP6SpyLogger.java new file mode 100644 index 00000000..7e6e8e89 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpCustomP6SpyLogger.java @@ -0,0 +1,66 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.mybatisplus.config; + +import cn.hutool.core.util.StrUtil; +import com.p6spy.engine.spy.appender.MessageFormattingStrategy; +import lombok.extern.slf4j.Slf4j; + +/** + * 自定义 p6spy sql输出格式 + */ +@Slf4j +public class MpCustomP6SpyLogger implements MessageFormattingStrategy { + /** + * 重置颜色 + */ + private static final String RESET = "\u001B[0m"; + /** + * 红色 + */ + private static final String RED = "\u001B[31m"; + /** + * 绿色 + */ + private static final String GREEN = "\u001B[32m"; + /** + * 蓝色 + */ + private static final String BLUE = "\u001B[34m"; + + /** + * 格式化 sql + * + * @param connectionId 连接id + * @param now 当前时间 + * @param elapsed 执行时长 + * @param category sql分类 + * @param prepared 预编译sql + * @param sql 执行sql + * @param url 数据库连接url + * @return 格式化后的sql + */ + @Override + public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) { + // 去掉换行和多余空格 + if (StrUtil.isNotBlank(sql)) { + sql = sql.replaceAll("\\s+", " ").trim(); + } + // 格式化并加上颜色 + return String.format("CuteJava - %s%s%s %s[%s] %sREVIEW_SQL [Interval: %dms]%s -> %s;", RED, now, RESET, GREEN, Thread.currentThread().getName(), BLUE, elapsed, RESET, sql); + } +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpInsertBatchSqlInjector.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpInsertBatchSqlInjector.java new file mode 100644 index 00000000..88c8402e --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpInsertBatchSqlInjector.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.mybatisplus.config; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.core.injector.AbstractMethod; +import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.extension.injector.methods.AlwaysUpdateSomeColumnById; +import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn; +import org.apache.ibatis.session.Configuration; + +import java.util.List; + +/** + * 批量插入SQL注入器 + * + * @author odboy + * @date 2025-01-11 + */ +public class MpInsertBatchSqlInjector extends DefaultSqlInjector { + @Override + public List getMethodList(Configuration configuration, Class mapperClass, TableInfo tableInfo) { + List methodList = super.getMethodList(configuration, mapperClass, tableInfo); + // 新增自定义方法, InsertBatchSomeColumn这个方法Mp没有开放出来 + methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE)); + // 全量更新, 包括null字段 + methodList.add(new AlwaysUpdateSomeColumnById(i -> i.getFieldFill() != FieldFill.INSERT)); + return methodList; + } +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MybatisPlusConfig.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MybatisPlusConfig.java new file mode 100644 index 00000000..86243018 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MybatisPlusConfig.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.mybatisplus.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableTransactionManagement +public class MybatisPlusConfig { + @Bean + public MybatisPlusInterceptor paginationInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // MySQL的分页拦截器 + PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); + paginationInnerInterceptor.setMaxLimit(500L); + /// 这个配置会导致:当分页没数据时, 游标从0开始,会导致无论page为什么值,就算分页超出最大页数,依然能够查询到 + /// paginationInnerInterceptor.setOverflow(true); + paginationInnerInterceptor.setOptimizeJoin(true); + interceptor.addInnerInterceptor(paginationInnerInterceptor); + // 乐观锁 + interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); + // 防全表更新与删除 + interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); + return interceptor; + } + + /** + * Mapper支持批量插入 + */ + @Bean + public MpInsertBatchSqlInjector insertBatchSqlInjector() { + return new MpInsertBatchSqlInjector(); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/RemoveDruidAdConfig.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/RemoveDruidAdConfig.java new file mode 100644 index 00000000..72dc7f02 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/RemoveDruidAdConfig.java @@ -0,0 +1,92 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.mybatisplus.config; + +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; +import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Configuration +@SuppressWarnings({"unchecked", "all"}) +@ConditionalOnWebApplication +@AutoConfigureAfter(DruidDataSourceAutoConfigure.class) +@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true", matchIfMissing = true) +public class RemoveDruidAdConfig { + + /** + * 方法名: removeDruidAdFilterRegistrationBean 方法描述 除去页面底部的广告 + * + * @param properties com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties + * @return org.springframework.boot.web.servlet.FilterRegistrationBean + */ + @Bean + public FilterRegistrationBean removeDruidAdFilterRegistrationBean(DruidStatProperties properties) { + + // 获取web监控页面的参数 + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + // 提取common.js的配置路径 + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + + final String filePath = "support/http/resources/js/common.js"; + + //创建filter进行过滤 + Filter filter = new Filter() { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + if (httpRequest.getRequestURI().endsWith("js/common.js")) { + // 获取common.js + String text = Utils.readFromResource(filePath); + // 正则替换banner, 除去底部的广告信息 + text = text.replaceAll("
", ""); + text = text.replaceAll("powered by.*?shrek.wang", ""); + httpResponse.setContentType("application/javascript"); + httpResponse.setCharacterEncoding("UTF-8"); + httpResponse.getWriter().write(text); + } else { + chain.doFilter(request, response); + } + } + + @Override + public void destroy() { + } + }; + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/CsMpAnyQUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/CsMpAnyQUtil.java new file mode 100644 index 00000000..eac33991 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/CsMpAnyQUtil.java @@ -0,0 +1,86 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.mybatisplus.core; + +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import java.util.ArrayList; +import java.util.List; + +/** + * 自定义任意对象查询工具 + * + * @author odboy + * @date 2025-05-15 + */ +public class CsMpAnyQUtil { + public static T selectOne(BaseMapper baseMapper, Object queryParams) { + QueryWrapper queryWrapper = CsMpQUtil.build(queryParams); + return baseMapper.selectOne(queryWrapper); + } + + public static M selectOne(BaseMapper baseMapper, Object queryParams, Class mapperClazz) { + QueryWrapper queryWrapper = CsMpQUtil.build(queryParams); + T mapperTarget = baseMapper.selectOne(queryWrapper); + if (mapperTarget == null) { + return null; + } + return BeanUtil.copyProperties(mapperTarget, mapperClazz); + } + + public static List selectList(BaseMapper baseMapper, Object queryParams) { + QueryWrapper queryWrapper = CsMpQUtil.build(queryParams); + return baseMapper.selectList(queryWrapper); + } + + public static List selectList(BaseMapper baseMapper, Object queryParams, Class mapperClazz) { + QueryWrapper queryWrapper = CsMpQUtil.build(queryParams); + List mapperTarget = baseMapper.selectList(queryWrapper); + if (mapperTarget == null) { + return new ArrayList<>(); + } + return BeanUtil.copyToList(mapperTarget, mapperClazz); + } + + public static Long selectCount(BaseMapper baseMapper, Object queryParams) { + QueryWrapper queryWrapper = CsMpQUtil.build(queryParams); + return baseMapper.selectCount(queryWrapper); + } + + public static Page selectPage(BaseMapper baseMapper, Page page, Object queryParams) { + QueryWrapper queryWrapper = CsMpQUtil.build(queryParams); + return baseMapper.selectPage(page, queryWrapper); + } + + public static Page selectPage( + BaseMapper baseMapper, + Page page, + Object queryParams, + Class mapperClazz) { + QueryWrapper queryWrapper = CsMpQUtil.build(queryParams); + Page tPage = baseMapper.selectPage(page, queryWrapper); + Page mapperTarget = new Page<>(); + mapperTarget.setRecords(BeanUtil.copyToList(tPage.getRecords(), mapperClazz)); + mapperTarget.setTotal(tPage.getTotal()); + mapperTarget.setSize(tPage.getSize()); + mapperTarget.setCurrent(tPage.getCurrent()); + return mapperTarget; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/CsMpCmdGenUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/CsMpCmdGenUtil.java new file mode 100644 index 00000000..33460b45 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/CsMpCmdGenUtil.java @@ -0,0 +1,228 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.mybatisplus.core; + +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.CsBaseUserTimeTb; +import cn.odboy.framework.exception.BadRequestException; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.generator.FastAutoGenerator; +import com.baomidou.mybatisplus.generator.config.*; +import com.baomidou.mybatisplus.generator.config.querys.MySqlQuery; +import com.baomidou.mybatisplus.generator.config.rules.DateType; +import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; +import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; +import com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine; +import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; +import com.baomidou.mybatisplus.generator.keywords.MySqlKeyWordsHandler; +import lombok.Data; +import org.apache.ibatis.annotations.Mapper; + +import java.sql.Types; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +/** + * 代码生成器入口 + * + * @author odboy + * @date 2025-01-11 + */ +@Data +public class CsMpCmdGenUtil { + // 项目根路径。生成结果 C:\Users\Administrator\Documents\GitHub\cutejava\cutejava + private static final String projectRootPath = System.getProperty("user.dir") + "/"; + /** + * 数据库类型 MySQL + */ + private static final IDbQuery DB_QUERY = new MySqlQuery(); + /** + * 类型转换 + */ + private static final String TYPE_TINY_INT = "tinyint"; + private static final String TYPE_DATETIME = "datetime"; + private static final String TYPE_DATE = "date"; + private String databaseUrl; + private String databaseUsername; + private String databasePassword; + private String driverClassName; + private String parentPackageName = "cn.odboy"; + private String parentModuleName; + + /** + * 代码生成函数 + * + * @param moduleName 模块名称 + * @param tablePrefix 表前缀(可空) + * @param tableNames 表名称, 多个用逗号分隔 + */ + public void gen(String moduleName, String tablePrefix, List tableNames) { + this.parentModuleName = moduleName; + // 1.数据库配置 + DataSourceConfig.Builder dataSourceConfigBuilder = + new DataSourceConfig.Builder(this.databaseUrl, this.databaseUsername, this.databasePassword).driverClassName( + this.driverClassName); + dataSourceConfigBuilder.dbQuery(DB_QUERY); + dataSourceConfigBuilder.keyWordsHandler(new MySqlKeyWordsHandler()); + // 1.1.快速生成器 + FastAutoGenerator fastAutoGenerator = FastAutoGenerator.create(dataSourceConfigBuilder); + // 2.全局配置 + // 自定义类型转换 + fastAutoGenerator.dataSourceConfig(getDataSourceConfigConsumer()); + // 覆盖已生成文件 + // 添加swagger注解 + // 设置注释的作者 + // 设置注释的日期格式 + // 使用java8新的时间类型 + fastAutoGenerator.globalConfig(getGlobalConfigConsumer()); + // 3.包配置 + // 设置父包名 + // 设置父包模块名 + // 设置MVC下各个模块的包名 + // 设置XML资源文件的目录 + fastAutoGenerator.packageConfig(getPackageConfigConsumer()); + // 4.模板配置 + // 使用Freemarker引擎模板, 默认的是Velocity引擎模板 + AbstractTemplateEngine templateEngine = new FreemarkerTemplateEngine(); + fastAutoGenerator.templateEngine(templateEngine); + // 5.策略配置 + // 设置需要生成的表名 + // 设置过滤表前缀 + fastAutoGenerator.strategyConfig(getStrategyConfigConsumer(tableNames)); + if (tablePrefix != null) { + // 表前缀, 可多个 + fastAutoGenerator.strategyConfig(strategyConfigBuilder -> strategyConfigBuilder.addTablePrefix(tablePrefix)); + } + // 5.1.Entity策略配置 + // 生成实体时生成字段的注解, 包括@TableId注解等 + // 数据库表和字段映射到实体的命名策略, 为下划线转驼峰 + // 全局主键类型为None + // 实体名称格式化为XXXEntity + fastAutoGenerator.strategyConfig(getEntityConfigConsumer()); + // 5.2.Controller策略配置 + // 开启生成@RestController控制器 + fastAutoGenerator.strategyConfig(strategyConfigBuilder -> strategyConfigBuilder.controllerBuilder() + .enableRestStyle()); + // 如果不需要生成Controller, fastAutoGenerator.templateConfig(templateConfig -> templateConfig.controller(""))。5.3.Service策略配置:格式化service接口和实现类的文件名称, 去掉默认的ServiceName前面的I + fastAutoGenerator.strategyConfig( + strategyConfigBuilder -> strategyConfigBuilder.serviceBuilder() + .formatServiceFileName("%sService") + .formatServiceImplFileName("%sServiceImpl")); + // 5.4.Mapper策略配置 + // 格式化 mapper文件名,格式化xml实现类文件名称 + fastAutoGenerator.strategyConfig(getStrategyConfigConsumer()); + // 6.生成代码 + fastAutoGenerator.execute(); + } + + private static Consumer getEntityConfigConsumer() { + return strategyConfigBuilder -> strategyConfigBuilder.entityBuilder() + .enableLombok() + // 如果不需要生成注解, 去掉.enableTableFieldAnnotation() + .enableTableFieldAnnotation() + .enableFileOverride() + .naming(NamingStrategy.underline_to_camel) + .columnNaming(NamingStrategy.underline_to_camel) + .idType(IdType.AUTO); + } + + private static Consumer getStrategyConfigConsumer(List tableNames) { + return strategyConfigBuilder -> strategyConfigBuilder.enableCapitalMode() + .enableSkipView() + .disableSqlFilter() + .addInclude(tableNames) + // 实体类生成策略 + .entityBuilder() + .superClass(CsBaseUserTimeTb.class) + .addIgnoreColumns("create_time", "update_time", "create_by", "update_by") + // 开启生成实体时生成字段注解。 + // 会在实体类的属性前,添加[@TableField("nickname")] + .enableTableFieldAnnotation() + .disableSerialVersionUID() + // 阶段2:Mapper策略配置 + .mapperBuilder() + // 开启 @Mapper 注解。 + // 会在mapper接口上添加注解[@Mapper] + // .enableMapperAnnotation() + // 启用 BaseResultMap 生成。 + // 会在mapper.xml文件生成[通用查询映射结果]配置。 + // .enableBaseResultMap() + // 启用 BaseColumnList。 + // 会在mapper.xml文件生成[通用查询结果列 ]配置 + // .enableBaseColumnList() + // 阶段4:Controller策略配置 + .controllerBuilder() + // 会在控制类中加[@RestController]注解。 + .enableRestStyle() + // 开启驼峰转连字符 + .enableHyphenStyle() + .build(); + } + + private Consumer getPackageConfigConsumer() { + String mapperPath = projectRootPath + "CodeGen/resources/mapper"; + if (StrUtil.isBlank(this.parentModuleName)) { + throw new BadRequestException("模块名称必填"); + } + return packageConfigBuilder -> packageConfigBuilder.parent(this.parentPackageName) + .moduleName(this.parentModuleName) + .entity("dal.dataobject") + .mapper("dal.mysql") + .service("service") + .serviceImpl("service.impl") + .controller("controller") + .pathInfo(Collections.singletonMap(OutputFile.xml, mapperPath)); + } + + private static Consumer getGlobalConfigConsumer() { + String outputDir = projectRootPath + "CodeGen"; + return globalConfigBuilder -> globalConfigBuilder.enableSwagger() + .outputDir(outputDir) + .author("codegen") + .commentDate("yyyy-MM-dd") + .dateType(DateType.TIME_PACK); + } + + private static Consumer getStrategyConfigConsumer() { + return strategyConfigBuilder -> strategyConfigBuilder.mapperBuilder() + .mapperAnnotation(Mapper.class) + .formatMapperFileName("%sMapper") + .formatXmlFileName("%sMapper") + .entityBuilder() + .formatFileName("%sTb"); + } + + private static Consumer getDataSourceConfigConsumer() { + return builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> { + int typeCode = metaInfo.getJdbcType().TYPE_CODE; + if (typeCode == Types.SMALLINT) { + return DbColumnType.INTEGER; + } + if (typeCode == Types.DATE) { + return DbColumnType.DATE; + } + if (typeCode == Types.TIME) { + return DbColumnType.DATE; + } + if (typeCode == Types.TIMESTAMP) { + return DbColumnType.DATE; + } + return typeRegistry.getColumnType(metaInfo); + }); + } +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/CsMpQUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/CsMpQUtil.java new file mode 100644 index 00000000..ad5317be --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/CsMpQUtil.java @@ -0,0 +1,231 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.mybatisplus.core; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.CsBaseUserTimeTb; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.mybatisplus.config.CsMpQuery; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.extern.slf4j.Slf4j; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 查询条件创建工具 + */ +@Slf4j +public class CsMpQUtil { + public static QueryWrapper build(Q query) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (query == null) { + return queryWrapper; + } + Class clazz = query.getClass(); + try { + List fields = getAllFields(clazz, new ArrayList<>()); + for (Field field : fields) { + if (!field.canAccess(query)) { + field.setAccessible(true); + CsMpQuery q = field.getAnnotation(CsMpQuery.class); + if (q != null) { + String attributeName = getAttributeName(q, field); + Object fieldVal = field.get(query); + if (ObjectUtil.isNull(fieldVal) || "".equals(fieldVal)) { + continue; + } + if (StrUtil.isNotBlank(q.blurry())) { + handleBlurryQuery(queryWrapper, q.blurry(), fieldVal); + continue; + } + handleWrapper(q, queryWrapper, attributeName, fieldVal); + } + field.setAccessible(false); + } + } + } catch (Exception e) { + log.error("Failed to build query wrapper for class: {}", clazz.getName(), e); + } + return queryWrapper; + } + + private static String getAttributeName(CsMpQuery q, Field field) { + String attributeName = StrUtil.isBlank(q.propName()) ? field.getName() : q.propName(); + return StrUtil.toUnderlineCase(attributeName); + } + + private static void handleWrapper( + CsMpQuery q, + QueryWrapper queryWrapper, + String attributeName, + Object fieldVal) { + switch (q.type()) { + case EQUAL: + queryWrapper.eq(attributeName, fieldVal); + break; + case GREATER_THAN: + queryWrapper.ge(attributeName, fieldVal); + break; + case LESS_THAN: + queryWrapper.le(attributeName, fieldVal); + break; + case LESS_THAN_NQ: + queryWrapper.lt(attributeName, fieldVal); + break; + case INNER_LIKE: + queryWrapper.like(attributeName, fieldVal); + break; + case LEFT_LIKE: + queryWrapper.likeLeft(attributeName, fieldVal); + break; + case RIGHT_LIKE: + queryWrapper.likeRight(attributeName, fieldVal); + break; + case IN: + handleInOrNotQuery(true, queryWrapper, attributeName, fieldVal); + break; + case NOT_IN: + handleInOrNotQuery(false, queryWrapper, attributeName, fieldVal); + break; + case NOT_EQUAL: + queryWrapper.ne(attributeName, fieldVal); + break; + case NOT_NULL: + queryWrapper.isNotNull(attributeName); + break; + case IS_NULL: + queryWrapper.isNull(attributeName); + break; + case BETWEEN: + handleBetweenQuery(queryWrapper, fieldVal, attributeName); + break; + default: + break; + } + } + + private static void handleInOrNotQuery( + boolean b, + QueryWrapper queryWrapper, + String attributeName, + Object fieldVal) { + Collection wrapNotInVal = (Collection) fieldVal; + if (CollectionUtil.isNotEmpty(wrapNotInVal)) { + Optional anyValOptional = wrapNotInVal.stream().findAny(); + if (anyValOptional.isPresent()) { + Object o = anyValOptional.get(); + if (o instanceof Long) { + if (b) { + queryWrapper.in(attributeName, fieldVal); + } else { + queryWrapper.notIn(attributeName, fieldVal); + } + } else if (o instanceof Integer) { + if (b) { + queryWrapper.in(attributeName, fieldVal); + } else { + queryWrapper.notIn(attributeName, fieldVal); + } + } else { + throw new BadRequestException("InOrNotIn查询参数类型的对象属性值必须是Long/Integer类型集合"); + } + } + } + } + + private static void handleBetweenQuery(QueryWrapper queryWrapper, Object fieldVal, String finalAttributeName) { + if (fieldVal instanceof List) { + List between = new ArrayList<>((List) fieldVal); + int minLength = 2; + if (CollectionUtil.isNotEmpty(between) && between.size() >= minLength) { + queryWrapper.between(finalAttributeName, between.get(0), between.get(1)); + } else { + throw new BadRequestException("BETWEEN类型的对象列表长度必须 >= 2"); + } + } else { + throw new BadRequestException("BETWEEN类型的对象必须是一个List子集合"); + } + } + + /** + * 模糊多字段 + * + * @param queryWrapper / + * @param blurry / + * @param fieldVal / + * @param / + */ + private static void handleBlurryQuery(QueryWrapper queryWrapper, String blurry, Object fieldVal) { + List blurryList = + Arrays.stream(blurry.split(",")).filter(StrUtil::isNotBlank).distinct().collect(Collectors.toList()); + queryWrapper.and(wrapper -> { + for (String blurryItem : blurryList) { + String column = StrUtil.toUnderlineCase(blurryItem); + wrapper.or(); + wrapper.like(column, fieldVal.toString()); + } + }); + } + + public static List getAllFields(Class clazz, List fields) { + if (clazz != null) { + fields.addAll(Arrays.asList(clazz.getDeclaredFields())); + getAllFields(clazz.getSuperclass(), fields); + } + return fields; + } + + @TableName("test_domain") + private static class TestDomain { + @NotNull(groups = CsBaseUserTimeTb.Update.class) + @TableId(value = "user_id", type = IdType.AUTO) + private Long id; + @TableField(value = "dept_id") + private Long deptId; + @NotBlank + private String username; + @NotBlank + private String nickName; + } + + public static void main(String[] args) { + QueryWrapper query = new QueryWrapper(); + query.or(wrapper -> wrapper.eq("username", 1).or().eq("nickname", 2)); + query.eq("id", 1); + query.orderByDesc("id"); + System.err.println("getSqlSelect================================="); + System.err.println(query.getSqlSelect()); + System.err.println("getSqlSegment================================="); + System.err.println(query.getSqlSegment()); + System.err.println("getSqlComment================================="); + System.err.println(query.getSqlComment()); + System.err.println("getSqlSet================================="); + System.err.println(query.getSqlSet()); + System.err.println("getTargetSql================================="); + System.err.println(query.getTargetSql()); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/CsMpMapper.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/CsMpMapper.java new file mode 100644 index 00000000..3c6d15b3 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/CsMpMapper.java @@ -0,0 +1,189 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.mybatisplus.core.interfaces; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.odboy.base.CsPageResult; +import cn.odboy.util.CsPageUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import org.springframework.data.domain.Pageable; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 公共抽象Mapper接口类 + * + * @author odboy + * @date 2022-04-03 + */ +public interface CsMpMapper extends BaseMapper { + /** + * 批量插入 + * + * @param entityList List + * @return 影响的行数 + */ + int insertBatchSomeColumn(List entityList); + + /** + * 根据id全量更新 + * + * @param entity E + * @return 影响的行数 + */ + int alwaysUpdateSomeColumnById(@Param("et") E entity); + + /** + * 获取字典 + * + * @param wrapper LambdaQueryWrapper + * @param keyName 键对应的列名称 + * @param valueName 值对应的列名称 + * @return Map + */ + default Map getMapWithKv(LambdaQueryWrapper wrapper, String keyName, String valueName) { + Map result = new ConcurrentHashMap<>(1); + List list = this.selectList(wrapper); + if (CollUtil.isEmpty(list)) { + return new HashMap<>(3); + } + E tempObj = list.stream().findFirst().orElse(null); + if (tempObj == null) { + return new HashMap<>(3); + } + Field[] fields = ReflectUtil.getFields(tempObj.getClass()); + for (E e : list) { + Field keyField = null; + Field valueField = null; + for (Field field : fields) { + String fieldName = field.getName(); + if (fieldName.equals(keyName)) { + keyField = field; + } else if (fieldName.equals(valueName)) { + valueField = field; + break; + } + } + String fieldKey = String.valueOf(ReflectUtil.getFieldValue(e, keyField)); + Object fieldValue = ReflectUtil.getFieldValue(e, valueField); + if (fieldKey != null) { + result.put(fieldKey, fieldValue); + } + } + return result; + } + + /** + * 条件查询列表, 并返回期望的对象 + * + * @param wrapper LambdaQueryWrapper + * @param clazz 期望的对象类型 + * @return List + */ + default List queryFeatureClazzList(LambdaQueryWrapper wrapper, Class clazz) { + List list = this.selectList(wrapper); + if (CollUtil.isEmpty(list)) { + return new ArrayList<>(); + } + return BeanUtil.copyToList(list, clazz); + } + + /** + * 条件查询列表, 并返回期望的对象 + * + * @param wrapper LambdaQueryWrapper + * @param clazz 期望的对象类型 + * @return List + */ + default List queryFeatureClazzList(LambdaQueryChainWrapper wrapper, Class clazz) { + List list = wrapper.list(); + if (CollUtil.isEmpty(list)) { + return new ArrayList<>(); + } + return BeanUtil.copyToList(list, clazz); + } + + /** + * 条件查询分页列表, 并返回期望的对象 + * + * @param pageable 分页参数 + * @param wrapper LambdaQueryWrapper wrapper + * @param clazz 期望的对象类型 + * @return IPage + */ + default CsPageResult queryFeatureClazzPage(Pageable pageable, LambdaQueryWrapper wrapper, Class clazz) { + int pageNumber = pageable.getPageNumber(); + int pageSize = pageable.getPageSize(); + pageNumber = pageNumber <= 0 ? 1 : pageNumber; + pageSize = pageSize <= 0 ? 10 : pageSize; + IPage pageInfo = this.selectPage(new Page<>(pageNumber, pageSize), wrapper); + if (CollUtil.isEmpty(pageInfo.getRecords())) { + return new CsPageResult<>(new ArrayList<>(), pageInfo.getTotal()); + } + return new CsPageResult<>(BeanUtil.copyToList(pageInfo.getRecords(), clazz), pageInfo.getTotal()); + } + + /** + * 条件查询分页列表, 并返回期望的对象 + * + * @param pageable 分页参数 + * @param wrapper LambdaQueryWrapper wrapper + * @param clazz 期望的对象类型 + * @return IPage + */ + default CsPageResult queryFeatureClazzPage( + Pageable pageable, + LambdaQueryChainWrapper wrapper, + Class clazz) { + int pageNumber = pageable.getPageNumber(); + int pageSize = pageable.getPageSize(); + pageNumber = pageNumber <= 0 ? 1 : pageNumber; + pageSize = pageSize <= 0 ? 10 : pageSize; + IPage pageInfo = wrapper.page(new Page<>(pageNumber, pageSize)); + if (CollUtil.isEmpty(pageInfo.getRecords())) { + return CsPageUtil.emptyListData(); + } + return new CsPageResult<>(BeanUtil.copyToList(pageInfo.getRecords(), clazz), pageInfo.getTotal()); + } + + /** + * 获取目标类 + * + * @param wrapper LambdaQueryChainWrapper wrapper + * @param clazz 期望的对象类型 + * @return T + */ + default T getFeatureClazz(LambdaQueryChainWrapper wrapper, Class clazz) { + E e = this.selectOne(wrapper); + if (e == null) { + return null; + } + return BeanUtil.copyProperties(e, clazz); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/CsMpService.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/CsMpService.java new file mode 100644 index 00000000..07e3bffe --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/CsMpService.java @@ -0,0 +1,74 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.mybatisplus.core.interfaces; + +import cn.odboy.base.CsPageResult; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * 公共抽象Service接口类 + * + * @author odboy + * @date 2022-04-03 + */ +public interface CsMpService extends IService { + int saveFeatureClazz(G resources); + + int saveFeatureClazzList(List resources); + + int modifyFeatureClazzById(G resources); + + boolean modifyFeatureClazzListById(Collection resources, int batchSize); + + G queryFeatureClazzById(Serializable id, Class targetClazz); + + T queryClazzByArgs(LambdaQueryWrapper wrapper, SFunction orderColumn); + + G queryFeatureClazzByArgs(LambdaQueryWrapper wrapper, SFunction orderColumn, Class clazz); + + List queryFeatureClazzListByIds(List ids, Class targetClazz); + + List queryClazzListByArgs(Q criteria); + + List queryClazzListByArgs(LambdaQueryWrapper wrapper); + + List queryFeatureClazzListByArgs(Q criteria, Class targetClazz); + + List queryFeatureClazzListByArgs(LambdaQueryWrapper wrapper, Class targetClazz); + + CsPageResult queryClazzPageByArgs(LambdaQueryWrapper wrapper, IPage pageable); + + CsPageResult queryFeatureClazzPageByArgs( + LambdaQueryWrapper wrapper, + IPage pageable, + Class targetClazz); + + CsPageResult queryFeatureClazzPageByArgs(Q criteria, IPage pageable, Class targetClazz); + + CsPageResult queryClazzPageByArgs(Q criteria, IPage pageable); + + int modifyClazzByArgs(LambdaQueryWrapper wrapper, T entity); + + int removeClazzByArgs(LambdaQueryWrapper wrapper); +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/CsMpServiceImpl.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/CsMpServiceImpl.java new file mode 100644 index 00000000..42266cf0 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/CsMpServiceImpl.java @@ -0,0 +1,259 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.mybatisplus.core.interfaces; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.odboy.base.CsPageResult; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.mybatisplus.core.CsMpQUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.enums.SqlMethod; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.apache.ibatis.binding.MapperMethod; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * 公共抽象Mapper接口类 + * + * @author odboy + * @date 2022-04-03 + */ +public class CsMpServiceImpl, T> extends ServiceImpl { + /** + * 快速插入 + * + * @param resources / + */ + public int saveFeatureClazz(G resources) { + return getBaseMapper().insert(BeanUtil.copyProperties(resources, this.getEntityClass())); + } + + /** + * 快速批量插入 + * + * @param resources / + */ + public int saveFeatureClazzList(List resources) { + return getBaseMapper().insertBatchSomeColumn(BeanUtil.copyToList(resources, this.getEntityClass())); + } + + /** + * 快速更新 + * + * @param resources / + */ + public int modifyFeatureClazzById(G resources) { + return getBaseMapper().updateById(BeanUtil.copyProperties(resources, this.getEntityClass())); + } + + /** + * 快速批量更新 + * + * @param resources / + * @param batchSize 每批数量 + */ + public boolean modifyFeatureClazzListById(Collection resources, int batchSize) { + String sqlStatement = this.getSqlStatement(SqlMethod.UPDATE_BY_ID); + return this.executeBatch(resources, batchSize, (sqlSession, entity) -> { + MapperMethod.ParamMap param = new MapperMethod.ParamMap<>(); + param.put("et", BeanUtil.copyProperties(entity, this.getEntityClass())); + sqlSession.update(sqlStatement, param); + }); + } + + /** + * 通过id查询 + * + * @param id / + * @param targetClazz 目标类型 + */ + public G queryFeatureClazzById(Serializable id, Class targetClazz) { + T entity = this.getById(id); + return entity == null ? null : BeanUtil.copyProperties(entity, targetClazz); + } + + /** + * 查一条数据 + * + * @param wrapper 制造条件 + */ + public T queryClazzByArgs(LambdaQueryWrapper wrapper, SFunction orderColumn) { + if (orderColumn == null) { + throw new BadRequestException("参数orderColumn(排序的列)必填"); + } + if (wrapper == null) { + wrapper = new LambdaQueryWrapper<>(); + } + wrapper.last("limit 1"); + wrapper.orderByDesc(orderColumn); + List ts = baseMapper.selectList(wrapper); + if (ts.isEmpty()) { + return null; + } + return ts.stream().findFirst().get(); + } + + public G queryFeatureClazzByArgs(LambdaQueryWrapper wrapper, SFunction orderColumn, Class clazz) { + if (orderColumn == null) { + throw new BadRequestException("参数orderColumn(排序的列)必填"); + } + if (wrapper == null) { + wrapper = new LambdaQueryWrapper<>(); + } + wrapper.last("limit 1"); + wrapper.orderByDesc(orderColumn); + List ts = baseMapper.selectList(wrapper); + if (ts.isEmpty()) { + return null; + } + return BeanUtil.copyProperties(ts.stream().findFirst().get(), clazz); + } + + /** + * 通过id集合查询 + * + * @param ids / + * @param targetClazz 目标类型 + */ + public List queryFeatureClazzListByIds(List ids, Class targetClazz) { + List entitys = this.listByIds(ids); + return entitys == null || entitys.isEmpty() ? CollUtil.newArrayList() : BeanUtil.copyToList(entitys, targetClazz); + } + + /** + * 查有关的数据 + * + * @param criteria 动态条件 + */ + public List queryClazzListByArgs(Q criteria) { + QueryWrapper predicate = CsMpQUtil.build(criteria); + return baseMapper.selectList(predicate); + } + + /** + * 查有关的数据 + * + * @param wrapper 制造条件 + */ + public List queryClazzListByArgs(LambdaQueryWrapper wrapper) { + if (wrapper == null) { + wrapper = new LambdaQueryWrapper<>(); + } + return baseMapper.selectList(wrapper); + } + + /** + * 查有关的数据并转成目标类型的数据 + * + * @param criteria 动态条件 + * @param targetClazz 目标类型 + */ + public List queryFeatureClazzListByArgs(Q criteria, Class targetClazz) { + QueryWrapper predicate = CsMpQUtil.build(criteria); + List data = baseMapper.selectList(predicate); + return BeanUtil.copyToList(data, targetClazz); + } + + /** + * 查有关的数据并转换为目标类型 + * + * @param wrapper 制造条件 + * @param targetClazz 目标类型 + */ + public List queryFeatureClazzListByArgs(LambdaQueryWrapper wrapper, Class targetClazz) { + if (wrapper == null) { + wrapper = new LambdaQueryWrapper<>(); + } + return BeanUtil.copyToList(baseMapper.selectList(wrapper), targetClazz); + } + + /** + * 查有关的分页数据 + * + * @param wrapper 制造条件 + * @param pageable 分页参数 + */ + public CsPageResult queryClazzPageByArgs(LambdaQueryWrapper wrapper, IPage pageable) { + if (wrapper == null) { + wrapper = new LambdaQueryWrapper<>(); + } + IPage originPageData = baseMapper.selectPage(pageable, wrapper); + return new CsPageResult<>(originPageData.getRecords(), originPageData.getTotal()); + } + + /** + * 查有关的分页数据 + * + * @param wrapper 制造条件 + * @param pageable 分页参数 + * @param targetClazz 目标类型 + */ + public CsPageResult queryFeatureClazzPageByArgs( + LambdaQueryWrapper wrapper, + IPage pageable, + Class targetClazz) { + if (wrapper == null) { + wrapper = new LambdaQueryWrapper<>(); + } + IPage originPageData = baseMapper.selectPage(pageable, wrapper); + return new CsPageResult<>(BeanUtil.copyToList(originPageData.getRecords(), targetClazz), originPageData.getTotal()); + } + + public CsPageResult queryFeatureClazzPageByArgs(Q criteria, IPage pageable, Class targetClazz) { + QueryWrapper wrapper = CsMpQUtil.build(criteria); + IPage originPageData = baseMapper.selectPage(pageable, wrapper); + return new CsPageResult<>(BeanUtil.copyToList(originPageData.getRecords(), targetClazz), originPageData.getTotal()); + } + + public CsPageResult queryClazzPageByArgs(Q criteria, IPage pageable) { + QueryWrapper wrapper = CsMpQUtil.build(criteria); + IPage originPageData = baseMapper.selectPage(pageable, wrapper); + return new CsPageResult<>(originPageData.getRecords(), originPageData.getTotal()); + } + + /** + * 条件更新 + * + * @param wrapper 制造条件 + * @param entity 更新内容 + */ + public int modifyClazzByArgs(LambdaQueryWrapper wrapper, T entity) { + if (wrapper == null) { + throw new BadRequestException("参数wrapper必填"); + } + return baseMapper.update(entity, wrapper); + } + + /** + * 条件删除 + * + * @param wrapper 制造条件 + */ + public int removeClazzByArgs(LambdaQueryWrapper wrapper) { + if (wrapper == null) { + throw new BadRequestException("参数wrapper必填"); + } + return baseMapper.delete(wrapper); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/AppProperties.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/AppProperties.java new file mode 100644 index 00000000..0e6a9738 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/AppProperties.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.properties; + +import cn.odboy.framework.properties.model.*; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 应用配置 + * + * @author odboy + * @date 2025-04-13 + */ +@Data +@Component +@ConfigurationProperties(prefix = "app") +public class AppProperties { + private ContentRsaEncodeSettingModel rsa; + private JwtAuthSettingModel jwt; + private UserLoginSettingModel login; + private SwaggerApiDocSettingModel swagger; + private ThreadPoolSettingModel asyncTaskPool; + private CaptchaModel captcha; + private StorageOSSModel oss; +} + + diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/CaptchaModel.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/CaptchaModel.java new file mode 100644 index 00000000..b4b36dc5 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/CaptchaModel.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.properties.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CaptchaModel extends CsObject { + /** + * 验证码有效时间, 单位: 秒 + */ + private Long expireTime; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ContentRsaEncodeSettingModel.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ContentRsaEncodeSettingModel.java new file mode 100644 index 00000000..5316102f --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ContentRsaEncodeSettingModel.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.properties.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +/** + * 密码加密传输, 前端公钥加密,后端私钥解密 + */ +@Getter +@Setter +public class ContentRsaEncodeSettingModel extends CsObject { + private String privateKey; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/JwtAuthSettingModel.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/JwtAuthSettingModel.java new file mode 100644 index 00000000..35b04607 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/JwtAuthSettingModel.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.properties.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +/** + * Jwt参数配置 + */ +@Getter +@Setter +public class JwtAuthSettingModel extends CsObject { + /** + * 必须使用最少88位的Base64对该令牌进行编码 + */ + private String base64Secret; + + /** + * 令牌过期时间 此处单位/毫秒 + */ + private Long tokenValidityInSeconds; + + /** + * token 续期检查 + */ + private Long detect; + + /** + * 续期时间 + */ + private Long renew; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/OSSConfigModel.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/OSSConfigModel.java new file mode 100644 index 00000000..d7e85f96 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/OSSConfigModel.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.properties.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class OSSConfigModel extends CsObject { + /** + * 服务地址 + */ + private String endpoint; + /** + * 存储桶名称 + */ + private String bucketName; + /** + * Access Key + */ + private String accessKey; + /** + * Secret Key + */ + private String secretKey; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/StorageOSSModel.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/StorageOSSModel.java new file mode 100644 index 00000000..6de87704 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/StorageOSSModel.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.properties.model; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class StorageOSSModel { + /** + * 上传大小, 单位: M + */ + private Long maxSize; + private OSSConfigModel minio; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/SwaggerApiDocSettingModel.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/SwaggerApiDocSettingModel.java new file mode 100644 index 00000000..e46ef7cf --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/SwaggerApiDocSettingModel.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.properties.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +/** + * @author odboy + * @date 2025-04-13 + */ +@Getter +@Setter +public class SwaggerApiDocSettingModel extends CsObject { + private Boolean enabled; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ThreadPoolSettingModel.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ThreadPoolSettingModel.java new file mode 100644 index 00000000..10ff0ba9 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ThreadPoolSettingModel.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.properties.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +/** + * 线程池 配置 + * + * @author odboy + * @date 2025-04-13 + */ +@Getter +@Setter +public class ThreadPoolSettingModel extends CsObject { + /** + * 核心线程池大小 + */ + private int corePoolSize; + /** + * 最大线程数 + */ + private int maxPoolSize; + /** + * 活跃时间 + */ + private int keepAliveSeconds; + /** + * 队列容量 + */ + private int queueCapacity; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginCaptchaSettingModel.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginCaptchaSettingModel.java new file mode 100644 index 00000000..01550abb --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginCaptchaSettingModel.java @@ -0,0 +1,132 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.properties.model; + +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.CsObject; +import cn.odboy.constant.CaptchaCodeEnum; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.util.CsStringUtil; +import com.wf.captcha.*; +import com.wf.captcha.base.Captcha; +import lombok.Getter; +import lombok.Setter; + +import java.awt.*; + +/** + * 验证码配置 + * + * @author odboy + */ +@Getter +@Setter +public class UserLoginCaptchaSettingModel extends CsObject { + private CaptchaCodeEnum codeType; + + /** + * 验证码有效期 分钟 + */ + private Long expiration = 5L; + + /** + * 验证码内容长度 + */ + private int length = 4; + + /** + * 验证码宽度 + */ + private int width = 111; + + /** + * 验证码高度 + */ + private int height = 36; + + /** + * 验证码字体 + */ + private String fontName; + + /** + * 字体大小 + */ + private int fontSize = 25; + + /** + * 依据配置信息生产验证码 + * + * @return / + */ + public Captcha getCaptcha() { + Captcha captcha; + switch (codeType) { + case ARITHMETIC: + // 算术类型 https://gitee.com/whvse/EasyCaptcha + captcha = new FixedArithmeticCaptcha(width, height); + // 几位数运算, 默认是两位 + captcha.setLen(length); + break; + case CHINESE: + captcha = new ChineseCaptcha(width, height); + captcha.setLen(length); + break; + case CHINESE_GIF: + captcha = new ChineseGifCaptcha(width, height); + captcha.setLen(length); + break; + case GIF: + captcha = new GifCaptcha(width, height); + captcha.setLen(length); + break; + case SPEC: + captcha = new SpecCaptcha(width, height); + captcha.setLen(length); + break; + default: + throw new BadRequestException("验证码配置信息错误!正确配置查看 LoginCodeEnum "); + } + if (StrUtil.isNotBlank(fontName)) { + captcha.setFont(new Font(fontName, Font.PLAIN, fontSize)); + } + return captcha; + } + + static class FixedArithmeticCaptcha extends ArithmeticCaptcha { + public FixedArithmeticCaptcha(int width, int height) { + super(width, height); + } + + @Override + protected char[] alphas() { + // 生成随机数字和运算符 + int n1 = num(1, 10), n2 = num(1, 10); + int opt = num(3); + + // 计算结果 + int res = new int[]{n1 + n2, n1 - n2, n1 * n2}[opt]; + // 转换为字符运算符 + char optChar = "+-x".charAt(opt); + + this.setArithmeticString(String.format("%s%c%s=?", n1, optChar, n2)); + this.chars = String.valueOf(res); + + return chars.toCharArray(); + } + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginSettingModel.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginSettingModel.java new file mode 100644 index 00000000..227b8cb5 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginSettingModel.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.properties.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +/** + * 登录配置 + * + * @author odboy + */ +@Getter +@Setter +public class UserLoginSettingModel extends CsObject { + /** + * 账号单用户 登录 + */ + private boolean single = false; + private UserLoginCaptchaSettingModel captchaSetting; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/quartz/config/QuartzConfig.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/quartz/config/QuartzConfig.java new file mode 100644 index 00000000..8fa66d6a --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/quartz/config/QuartzConfig.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.quartz.config; + +import lombok.extern.slf4j.Slf4j; +import org.quartz.spi.TriggerFiredBundle; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.lang.NonNull; +import org.springframework.scheduling.quartz.AdaptableJobFactory; +import org.springframework.stereotype.Component; + +/** + * 定时任务配置 + */ +@Slf4j +@Configuration +@Scope("singleton") +public class QuartzConfig { + /** + * 解决Job中注入Spring Bean为null的问题 + */ + @Component("quartzJobFactory") + public static class QuartzJobFactory extends AdaptableJobFactory { + + private final AutowireCapableBeanFactory autowireCapableBeanFactory; + + @Autowired + public QuartzJobFactory(AutowireCapableBeanFactory autowireCapableBeanFactory) { + this.autowireCapableBeanFactory = autowireCapableBeanFactory; + } + + @NonNull + @Override + protected Object createJobInstance(@NonNull TriggerFiredBundle triggerFiredBundle) throws Exception { + try { + // 调用父类的方法, 把Job注入Spring中 + Object jobInstance = super.createJobInstance(triggerFiredBundle); + autowireCapableBeanFactory.autowireBean(jobInstance); + return jobInstance; + } catch (Exception e) { + log.error("Job注入Spring失败, {}", triggerFiredBundle, e); + throw e; + } + } + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/quartz/core/QuartzRunnable.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/quartz/core/QuartzRunnable.java new file mode 100644 index 00000000..d66c7604 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/quartz/core/QuartzRunnable.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.quartz.core; + +import cn.odboy.framework.context.CsSpringBeanHolder; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; + +/** + * 执行定时任务 + */ +@Slf4j +public class QuartzRunnable implements Callable { + private final Object target; + private final Method method; + private final String params; + + public QuartzRunnable(String beanName, String methodName, String params) + throws NoSuchMethodException, SecurityException { + this.target = CsSpringBeanHolder.getBean(beanName); + this.params = params; + if (StringUtils.isNotBlank(params)) { + this.method = target.getClass().getDeclaredMethod(methodName, String.class); + } else { + this.method = target.getClass().getDeclaredMethod(methodName); + } + } + + @Override + @SuppressWarnings({"unchecked", "all"}) + public Object call() throws Exception { + ReflectionUtils.makeAccessible(method); + if (StringUtils.isNotBlank(params)) { + return method.invoke(target, params); + } else { + return method.invoke(target); + } + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/redis/CsRedisHelper.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/redis/CsRedisHelper.java new file mode 100644 index 00000000..07c9835a --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/redis/CsRedisHelper.java @@ -0,0 +1,843 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.redis; + +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.*; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Component +@SuppressWarnings({"unchecked", "all"}) +public class CsRedisHelper { + private static final Logger log = LoggerFactory.getLogger(CsRedisHelper.class); + + private RedisTemplate redisTemplate; + + public CsRedisHelper(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + this.redisTemplate.setKeySerializer(new StringRedisSerializer()); + this.redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + } + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) 注意:这里将会替换原有的时间 + */ + public boolean expire(String key, long time) { + try { + if (time > 0) { + redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + return true; + } + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) 注意:这里将会替换原有的时间 + * @param timeUnit 单位 + */ + public boolean expire(String key, long time, TimeUnit timeUnit) { + try { + if (time > 0) { + redisTemplate.expire(key, time, timeUnit); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + return true; + } + + /** + * 根据 key 获取过期时间 + * + * @param key 键 不能为null + * @return 时间(秒) 返回0代表为永久有效 + */ + public long getExpire(Object key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + /** + * 查找匹配key + * + * @param pattern key + * @return / + */ + public List scan(String pattern) { + ScanOptions options = ScanOptions.scanOptions().match(pattern).build(); + RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); + RedisConnection rc = Objects.requireNonNull(factory).getConnection(); + Cursor cursor = rc.scan(options); + List result = new ArrayList<>(); + while (cursor.hasNext()) { + result.add(new String(cursor.next())); + } + try { + RedisConnectionUtils.releaseConnection(rc, factory); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return result; + } + + /** + * 分页查询 key + * + * @param patternKey key + * @param page 页码 + * @param size 每页数目 + * @return / + */ + public List findKeysForPage(String patternKey, int page, int size) { + ScanOptions options = ScanOptions.scanOptions().match(patternKey).build(); + RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); + RedisConnection rc = Objects.requireNonNull(factory).getConnection(); + Cursor cursor = rc.scan(options); + List result = new ArrayList<>(size); + int tmpIndex = 0; + int fromIndex = page * size; + int toIndex = page * size + size; + while (cursor.hasNext()) { + if (tmpIndex >= fromIndex && tmpIndex < toIndex) { + result.add(new String(cursor.next())); + tmpIndex++; + continue; + } + // 获取到满足条件的数据后,就可以退出了 + if (tmpIndex >= toIndex) { + break; + } + tmpIndex++; + cursor.next(); + } + try { + RedisConnectionUtils.releaseConnection(rc, factory); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return result; + } + + /** + * 判断key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public boolean hasKey(String key) { + try { + return redisTemplate.hasKey(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 删除缓存 + * + * @param key 可以传一个值 或多个 + */ + public void del(String... keys) { + if (keys != null && keys.length > 0) { + if (keys.length == 1) { + boolean result = redisTemplate.delete(keys[0]); + log.debug("--------------------------------------------"); + log.debug(new StringBuilder("删除缓存:").append(keys[0]).append(", 结果:").append(result).toString()); + log.debug("--------------------------------------------"); + } else { + Set keySet = new HashSet<>(); + for (String key : keys) { + if (redisTemplate.hasKey(key)) { + keySet.add(key); + } + } + long count = redisTemplate.delete(keySet); + log.debug("--------------------------------------------"); + log.debug("成功删除缓存:" + keySet.toString()); + log.debug("缓存删除数量:" + count + "个"); + log.debug("--------------------------------------------"); + } + } + } + + /** + * 批量模糊删除key + * + * @param pattern + */ + public void scanDel(String pattern) { + ScanOptions options = ScanOptions.scanOptions().match(pattern).build(); + try (Cursor cursor = redisTemplate.executeWithStickyConnection( + (RedisCallback>) connection -> (Cursor) new ConvertingCursor<>( + connection.scan(options), + redisTemplate.getKeySerializer()::deserialize))) { + while (cursor.hasNext()) { + redisTemplate.delete(cursor.next()); + } + } + } + + // ============================String============================= + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public Object get(String key) { + return key == null ? null : redisTemplate.opsForValue().get(key); + } + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public T get(String key, Class clazz) { + Object value = key == null ? null : redisTemplate.opsForValue().get(key); + if (value == null) { + return null; + } + // 如果 value 不是目标类型, 则尝试将其反序列化为 clazz 类型 by + if (!clazz.isInstance(value)) { + return JSON.parseObject(value.toString(), clazz); + } else if (clazz.isInstance(value)) { + return clazz.cast(value); + } else { + return null; + } + } + + /** + * 普通缓存获取 + * + * @param key 键 + * @param clazz 列表中元素的类型 + * @return 值 + */ + public List getList(String key, Class clazz) { + Object value = key == null ? null : redisTemplate.opsForValue().get(key); + if (value == null) { + return null; + } + if (value instanceof List) { + List list = (List) value; + // 检查每个元素是否为指定类型 + if (list.stream().allMatch(clazz::isInstance)) { + return list.stream().map(clazz::cast).collect(Collectors.toList()); + } + } + return null; + } + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public String getStr(String key) { + if (StrUtil.isBlank(key)) { + return null; + } + Object value = redisTemplate.opsForValue().get(key); + if (value == null) { + return null; + } else { + return String.valueOf(value); + } + } + + /** + * 批量获取 + * + * @param keys + * @return + */ + public List multiGet(List keys) { + List list = redisTemplate.opsForValue().multiGet(Sets.newHashSet(keys)); + List resultList = Lists.newArrayList(); + Optional.ofNullable(list).ifPresent(e -> list.forEach(ele -> Optional.ofNullable(ele).ifPresent(resultList::add))); + return resultList; + } + + /** + * 普通缓存放入 + * + * @param key 键 + * @param value 值 + * @return true成功 false失败 + */ + public boolean set(String key, Object value) { + int attempt = 0; + while (attempt < 3) { + try { + redisTemplate.opsForValue().set(key, value); + return true; + } catch (Exception e) { + attempt++; + log.error("Attempt {} failed: {}", attempt, e.getMessage(), e); + } + } + return false; + } + + /** + * 普通缓存放入并设置时间 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期, 注意:这里将会替换原有的时间 + * @return true成功 false 失败 + */ + public boolean set(String key, Object value, long time) { + try { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); + } else { + set(key, value); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 普通缓存放入并设置时间 + * + * @param key 键 + * @param value 值 + * @param time 时间,注意:这里将会替换原有的时间 + * @param timeUnit 类型 + * @return true成功 false 失败 + */ + public boolean set(String key, Object value, long time, TimeUnit timeUnit) { + try { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, timeUnit); + } else { + set(key, value); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + // ================================Map================================= + + /** + * HashGet + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return 值 + */ + public Object hget(String key, String item) { + return redisTemplate.opsForHash().get(key, item); + } + + /** + * 获取hashKey对应的所有键值 + * + * @param key 键 + * @return 对应的多个键值 + */ + public Map hmget(String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * HashSet + * + * @param key 键 + * @param map 对应多个键值 + * @return true 成功 false 失败 + */ + public boolean hmset(String key, Map map) { + try { + redisTemplate.opsForHash().putAll(key, map); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * HashSet + * + * @param key 键 + * @param map 对应多个键值 + * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 + * @return true成功 false失败 + */ + public boolean hmset(String key, Map map, long time) { + try { + redisTemplate.opsForHash().putAll(key, map); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value) { + try { + redisTemplate.opsForHash().put(key, item, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value, long time) { + try { + redisTemplate.opsForHash().put(key, item, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 删除hash表中的值 + * + * @param key 键 不能为null + * @param item 项 可以使多个 不能为null + */ + public void hdel(String key, Object... item) { + redisTemplate.opsForHash().delete(key, item); + } + + /** + * 判断hash表中是否有该项的值 + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return true 存在 false不存在 + */ + public boolean hHasKey(String key, String item) { + return redisTemplate.opsForHash().hasKey(key, item); + } + + /** + * hash递增 如果不存在,就会创建一个 并把新增后的值返回 + * + * @param key 键 + * @param item 项 + * @param by 要增加几(大于0) + * @return + */ + public double hincr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, by); + } + + /** + * hash递减 + * + * @param key 键 + * @param item 项 + * @param by 要减少记(小于0) + * @return + */ + public double hdecr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, -by); + } + + // ============================set============================= + + /** + * 根据key获取Set中的所有值 + * + * @param key 键 + * @return + */ + public Set sGet(String key) { + try { + return redisTemplate.opsForSet().members(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } + } + + /** + * 根据value从一个set中查询,是否存在 + * + * @param key 键 + * @param value 值 + * @return true 存在 false不存在 + */ + public boolean sHasKey(String key, Object value) { + try { + return redisTemplate.opsForSet().isMember(key, value); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将数据放入set缓存 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSet(String key, Object... values) { + try { + return redisTemplate.opsForSet().add(key, values); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 将set数据放入缓存 + * + * @param key 键 + * @param time 时间(秒) 注意:这里将会替换原有的时间 + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSetAndTime(String key, long time, Object... values) { + try { + Long count = redisTemplate.opsForSet().add(key, values); + if (time > 0) { + expire(key, time); + } + return count; + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 获取set缓存的长度 + * + * @param key 键 + * @return + */ + public long sGetSetSize(String key) { + try { + return redisTemplate.opsForSet().size(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 移除值为value的 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 移除的个数 + */ + public long setRemove(String key, Object... values) { + try { + Long count = redisTemplate.opsForSet().remove(key, values); + return count; + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + // ===============================list================================= + + /** + * 获取list缓存的内容 + * + * @param key 键 + * @param start 开始 + * @param end 结束 0 到 -1代表所有值 + * @return + */ + public List lGet(String key, long start, long end) { + try { + return redisTemplate.opsForList().range(key, start, end); + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } + } + + /** + * 获取list缓存的长度 + * + * @param key 键 + * @return + */ + public long lGetListSize(String key) { + try { + return redisTemplate.opsForList().size(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 通过索引 获取list中的值 + * + * @param key 键 + * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 + * @return + */ + public Object lGetIndex(String key, long index) { + try { + return redisTemplate.opsForList().index(key, index); + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, Object value) { + try { + redisTemplate.opsForList().rightPush(key, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) 注意:这里将会替换原有的时间 + * @return + */ + public boolean lSet(String key, Object value, long time) { + try { + redisTemplate.opsForList().rightPush(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, List value) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) 注意:这里将会替换原有的时间 + * @return + */ + public boolean lSet(String key, List value, long time) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 根据索引修改list中的某条数据 + * + * @param key 键 + * @param index 索引 + * @param value 值 + * @return / + */ + public boolean lUpdateIndex(String key, long index, Object value) { + try { + redisTemplate.opsForList().set(key, index, value); + return true; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 移除N个值为value + * + * @param key 键 + * @param count 移除多少个 + * @param value 值 + * @return 移除的个数 + */ + public long lRemove(String key, long count, Object value) { + try { + return redisTemplate.opsForList().remove(key, count, value); + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * @param prefix 前缀 + * @param ids id + */ + public void delByKeys(String prefix, Set ids) { + Set keys = new HashSet<>(); + for (Long id : ids) { + keys.addAll(redisTemplate.keys(new StringBuffer(prefix).append(id).toString())); + } + long count = redisTemplate.delete(keys); + } + + // ============================incr============================= + + /** + * 递增 + * + * @param key + * @return + */ + public Long increment(String key) { + return redisTemplate.opsForValue().increment(key); + } + + /** + * 递减 + * + * @param key + * @return + */ + public Long decrement(String key) { + return redisTemplate.opsForValue().decrement(key); + } + + // ============================校验============================= + public boolean isHealth() { + try { + redisTemplate.getConnectionFactory().getConnection().ping(); + return true; + } catch (Exception e) { + log.error("Redis health check failed: {}", e.getMessage(), e); + return false; + } + } + + /** + * 仅当键不存在时设置值(原子操作) + * + * @param key 键 + * @param value 值 + * @param timeout 过期时间 + * @param unit 时间单位 + * @return 如果设置成功返回true,否则返回false + */ + public Boolean setIfAbsent(String key, Object value, long timeout, TimeUnit unit) { + try { + String stringValue = ObjUtil.toString(value); + // 使用Redis的SETNX命令实现原子性操作 + Boolean success = redisTemplate.opsForValue().setIfAbsent(key, stringValue, timeout, unit); + return success != null && success; + } catch (Exception e) { + log.error("Redis setIfAbsent操作失败, key: {}", key, e); + return false; + } + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/redis/RedisConfiguration.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/redis/RedisConfiguration.java new file mode 100644 index 00000000..73c3e0d0 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/redis/RedisConfiguration.java @@ -0,0 +1,202 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.redis; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONFactory; +import com.alibaba.fastjson2.JSONWriter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.MurmurHash3; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.cache.Cache; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.interceptor.CacheErrorHandler; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.cache.interceptor.SimpleCacheErrorHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@Configuration +@EnableCaching +@AutoConfigureBefore(RedisAutoConfiguration.class) +public class RedisConfiguration extends CachingConfigurerSupport { + /** + * 自动识别json对象白名单配置(仅允许解析的包名, 范围越小越安全)
未配置可能导致, 登录失败, 反复登录等问题 + */ + private static final String[] WHITELIST_STR = + { + "org.springframework", + "cn.odboy.system.dal.dataobject", + "cn.odboy.system.dal.model", + "cn.odboy.task.dal.dataobject", + "cn.odboy.task.dal.model", + }; + + /** + * 设置 redis 数据默认过期时间,默认2小时 设置@cacheable 序列化方式 + */ + @Bean + public RedisCacheConfiguration redisCacheConfiguration() { + FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); + RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig(); + configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofHours(2)); + return configuration; + } + + @Bean(name = "redisTemplate") + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + // 指定 key 和 value 的序列化方案 + FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); + // value值的序列化采用fastJsonRedisSerializer + template.setValueSerializer(fastJsonRedisSerializer); + template.setHashValueSerializer(fastJsonRedisSerializer); + // 设置fastJson的序列化白名单 + for (String pack : WHITELIST_STR) { + JSONFactory.getDefaultObjectReaderProvider().addAutoTypeAccept(pack); + } + // key的序列化采用StringRedisSerializer + template.setKeySerializer(new StringRedisSerializer()); + template.setHashKeySerializer(new StringRedisSerializer()); + template.setConnectionFactory(redisConnectionFactory); + return template; + } + + /** + * 缓存管理器 + * + * @param redisConnectionFactory / + * @return 缓存管理器 + */ + @Bean + public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { + RedisCacheConfiguration config = redisCacheConfiguration(); + return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build(); + } + + /** + * 自定义缓存key生成策略 + */ + + @Bean + @Override + public KeyGenerator keyGenerator() { + return (target, method, params) -> { + Map container = new HashMap<>(8); + Class targetClassClass = target.getClass(); + // 类地址 + container.put("class", targetClassClass.toGenericString()); + // 方法名称 + container.put("methodName", method.getName()); + // 包名称 + container.put("package", targetClassClass.getPackage()); + // 参数列表 + for (int i = 0; i < params.length; i++) { + container.put(String.valueOf(i), params[i]); + } + // 转为JSON字符串 + String jsonString = JSON.toJSONString(container); + // 使用 MurmurHash 生成 hash + return Integer.toHexString(MurmurHash3.hash32x86(jsonString.getBytes())); + }; + } + + @Bean + @SuppressWarnings({"unchecked", "all"}) + public CacheErrorHandler errorHandler() { + return new SimpleCacheErrorHandler() { + @Override + public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) { + // 处理缓存读取错误 + log.error("Cache Get Error: {}", exception.getMessage()); + } + + @Override + public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) { + // 处理缓存写入错误 + log.error("Cache Put Error: {}", exception.getMessage()); + } + + @Override + public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) { + // 处理缓存删除错误 + log.error("Cache Evict Error: {}", exception.getMessage()); + } + + @Override + public void handleCacheClearError(RuntimeException exception, Cache cache) { + // 处理缓存清除错误 + log.error("Cache Clear Error: {}", exception.getMessage()); + } + }; + } + + /** + * Value 序列化 + * + * @param + */ + static class FastJsonRedisSerializer implements RedisSerializer { + + private final Class clazz; + + FastJsonRedisSerializer(Class clazz) { + super(); + this.clazz = clazz; + } + + /** + * 序列化 + */ + @Override + public byte[] serialize(T t) throws SerializationException { + if (t == null) { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(StandardCharsets.UTF_8); + } + + /** + * 反序列化 + */ + @Override + public T deserialize(byte[] bytes) throws SerializationException { + if (bytes == null || bytes.length == 0) { + return null; + } + String str = new String(bytes, StandardCharsets.UTF_8); + // 移除对 SupportAutoType 的使用 + return JSON.parseObject(str, clazz); + } + } +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/config/AsyncTheadPoolConfig.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/config/AsyncTheadPoolConfig.java new file mode 100644 index 00000000..96d9a5e1 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/config/AsyncTheadPoolConfig.java @@ -0,0 +1,101 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.server.config; + +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.properties.model.ThreadPoolSettingModel; +import com.alibaba.ttl.TtlRunnable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 创建自定义的线程池 + */ +@EnableAsync +@Configuration +public class AsyncTheadPoolConfig implements AsyncConfigurer { + @Autowired + private AppProperties properties; + + /** + * 自定义线程池, 用法 @Async + * + * @return Executor + */ + @Override + public Executor getAsyncExecutor() { + ThreadPoolSettingModel asyncTaskPool = properties.getAsyncTaskPool(); + // 自定义工厂 + ThreadFactory factory = r -> new Thread(r, "DefaultAsync-" + new AtomicInteger(1).getAndIncrement()); + // 自定义线程池 + return new ThreadPoolExecutor( + asyncTaskPool.getCorePoolSize(), + asyncTaskPool.getMaxPoolSize(), + asyncTaskPool.getKeepAliveSeconds(), + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(asyncTaskPool.getQueueCapacity()), + factory, + new ThreadPoolExecutor.CallerRunsPolicy()); + } + + /** + * 自定义线程池, 用法, 注入到类中使用 + */ + @Bean("taskAsync") + public ThreadPoolTaskExecutor taskAsync() { + ThreadPoolSettingModel asyncTaskPool = properties.getAsyncTaskPool(); + // 用法 private ThreadPoolTaskExecutor taskExecutor + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(asyncTaskPool.getCorePoolSize()); + executor.setMaxPoolSize(asyncTaskPool.getMaxPoolSize()); + executor.setQueueCapacity(asyncTaskPool.getQueueCapacity()); + executor.setThreadNamePrefix("TaskAsync-"); + executor.setKeepAliveSeconds(asyncTaskPool.getKeepAliveSeconds()); + // DiscardOldestPolicy,抛弃最早的任务,将新任务加入队列。 + // AbortPolicy,拒绝执行新任务,并抛出异常。 + // CallerRunsPolicy,交由调用者线程执行新任务,如果调用者线程已关闭,则抛弃任务。 + // DiscardPolicy,直接抛弃新任务。 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); + // 设置包装器 + executor.setTaskDecorator(TtlRunnable::get); + executor.initialize(); + return executor; + } + + @Bean("longTimeTaskAsync") + public ThreadPoolTaskExecutor longTimeTaskAsync() { + ThreadPoolSettingModel asyncTaskPool = properties.getAsyncTaskPool(); + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(asyncTaskPool.getCorePoolSize()); + executor.setMaxPoolSize(asyncTaskPool.getMaxPoolSize()); + executor.setQueueCapacity(asyncTaskPool.getQueueCapacity()); + executor.setThreadNamePrefix("LongTimeTaskAsync-"); + executor.setKeepAliveSeconds(asyncTaskPool.getKeepAliveSeconds()); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); + // 设置包装器 + executor.setTaskDecorator(TtlRunnable::get); + executor.initialize(); + return executor; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringArraysToListDateConverter.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringArraysToListDateConverter.java new file mode 100644 index 00000000..f58b01cd --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringArraysToListDateConverter.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.server.converters; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class DateStringArraysToListDateConverter implements Converter> { + private static final ThreadLocal SDF = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); + + @Override + public List convert(String[] source) { + if (source.length == 0) { + return Collections.emptyList(); + } + return Arrays.stream(source).map(s -> { + try { + return SDF.get().parse(s); + } catch (ParseException e) { + throw new IllegalArgumentException("Invalid date string: " + s); + } + }).collect(Collectors.toList()); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringToDateConverter.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringToDateConverter.java new file mode 100644 index 00000000..e7fe40ba --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringToDateConverter.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.server.converters; + +import cn.hutool.core.util.StrUtil; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +@Component +public class DateStringToDateConverter implements Converter { + private static final ThreadLocal SDF = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); + + @Override + public Date convert(String source) { + if (StrUtil.isBlank(source)) { + return null; + } + try { + return SDF.get().parse(source); + } catch (ParseException e) { + throw new IllegalArgumentException("Invalid date string: " + source); + } + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/core/ConfigurerAdapter.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/core/ConfigurerAdapter.java new file mode 100644 index 00000000..6c6ab1e7 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/core/ConfigurerAdapter.java @@ -0,0 +1,123 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.server.core; + +import cn.odboy.framework.server.converters.DateStringArraysToListDateConverter; +import cn.odboy.framework.server.converters.DateStringToDateConverter; +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.support.config.FastJsonConfig; +import com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * 跨越、静态资源映射、Http消息转换 + * + * @author odboy + * @date 2025-03-21 + */ +@Configuration +@EnableWebMvc +public class ConfigurerAdapter implements WebMvcConfigurer { + @Autowired + private DateStringArraysToListDateConverter dateStringArraysToListDateConverter; + @Autowired + private DateStringToDateConverter dateStringToDateConverter; + @Autowired + private CsFileLocalUploadHelper fileUploadPathHelper; + + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOriginPattern("*"); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + String path = fileUploadPathHelper.getPath(); + String pathUtl = "file:" + path.replace("\\", "/"); + registry.addResourceHandler("/avatar/**").addResourceLocations(pathUtl).setCachePeriod(0); + registry.addResourceHandler("/file/**").addResourceLocations(pathUtl).setCachePeriod(0); + registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0); + } + + /** + * Http消息转换器 + */ + @Override + public void configureMessageConverters(List> converters) { + // 添加默认的 StringHttpMessageConverter + converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8)); + // 配置 FastJsonHttpMessageConverter + FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter(); + List supportMediaTypeList = new ArrayList<>(); + supportMediaTypeList.add(MediaType.APPLICATION_JSON); + FastJsonConfig config = new FastJsonConfig(); + config.setDateFormat("yyyy-MM-dd HH:mm:ss"); + // 开启引用检测, 枚举支持 + /// 是否输出值为null的字段 +// config.setWriterFeatures(JSONWriter.Feature.WriteMapNullValue); + /// 字段如果为null,输出为false,而非null +// config.setWriterFeatures(JSONWriter.Feature.WriteNullBooleanAsFalse); + /// 字段如果为null,输出为[],而非null +// config.setWriterFeatures(JSONWriter.Feature.WriteNullListAsEmpty); + /// 字符类型字段如果为null,输出为"",而非null +// config.setWriterFeatures(JSONWriter.Feature.WriteNullStringAsEmpty); + /// 太具体的数值会直接影响逻辑本身, 所以不要这个 +// config.setWriterFeatures(JSONWriter.Feature.WriteNullNumberAsZero); + /// 全局Long转字符串,会导致基础框架本身异常, 所以不要这个 +// config.setWriterFeatures(JSONWriter.Feature.WriteLongAsString); + config.setWriterFeatures(JSONWriter.Feature.WriteEnumUsingToString); + config.setWriterFeatures(JSONWriter.Feature.ReferenceDetection); + fastJsonConverter.setFastJsonConfig(config); + fastJsonConverter.setSupportedMediaTypes(supportMediaTypeList); + fastJsonConverter.setDefaultCharset(StandardCharsets.UTF_8); + // 将 FastJsonHttpMessageConverter 添加到列表末尾 + converters.add(fastJsonConverter); + } + + /** + * 自定义时间转换器 + */ + @Override + public void addFormatters(FormatterRegistry registry) { + registry.addConverter(dateStringArraysToListDateConverter); + registry.addConverter(dateStringToDateConverter); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/core/CsFileLocalUploadHelper.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/core/CsFileLocalUploadHelper.java new file mode 100644 index 00000000..a80fd7f4 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/server/core/CsFileLocalUploadHelper.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.server.core; + +import cn.odboy.constant.SystemConst; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * 简化文件上传路径配置 + * + * @author odboy + */ +@Component +public class CsFileLocalUploadHelper { + /** + * 单位MB + */ + private static final int AVATAR_MAX_SIZE = 5; + private static final String PATH_WINDOWS = "C:\\${appName}\\file\\"; + private static final String PATH_MAC = "/Users/odboy/${appName}/file/"; + private static final String PATH_LINUX = "/home/${appName}/file/"; + @Value("${spring.application.name}") + private String appName; + + public String getPath() { + String os = System.getProperty("os.name"); + if (os.toLowerCase().startsWith(SystemConst.WIN)) { + return PATH_WINDOWS.replace("${appName}", appName); + } else if (os.toLowerCase().startsWith(SystemConst.MAC)) { + return PATH_MAC.replace("${appName}", appName); + } + return PATH_LINUX.replace("${appName}", appName); + } + + public int getAvatarMaxSize() { + return AVATAR_MAX_SIZE; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/config/WsConfig.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/config/WsConfig.java new file mode 100644 index 00000000..53a2e9e4 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/config/WsConfig.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.websocket.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +@Configuration +public class WsConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/config/WsFactory.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/config/WsFactory.java new file mode 100644 index 00000000..0044f8c7 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/config/WsFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.websocket.config; + +import io.undertow.server.DefaultByteBufferPool; +import io.undertow.websockets.jsr.WebSocketDeploymentInfo; +import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.stereotype.Component; + +/** + * 解决启动io.undertow.websockets.jsr UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used的警告 + * + * @date 2024-06-07 + */ +@Component +public class WsFactory implements WebServerFactoryCustomizer { + + @Override + public void customize(UndertowServletWebServerFactory factory) { + factory.addDeploymentInfoCustomizers(deploymentInfo -> { + WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo(); + webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 1024)); + deploymentInfo.addServletContextAttribute( + "io.undertow.websockets.jsr.WebSocketDeploymentInfo", + webSocketDeploymentInfo); + }); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/CsWsClientManager.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/CsWsClientManager.java new file mode 100644 index 00000000..60885107 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/CsWsClientManager.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.websocket.context; + +import lombok.extern.slf4j.Slf4j; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * WebSocket客户端管理 + * + * @author odboy + * @date 2025-07-24 + */ +@Slf4j +public class CsWsClientManager { + /** + * concurrent包的线程安全Map, 用来存放每个客户端对应的MyWebSocket对象。(分布式必出问题) + */ + private static final Map clientMap = new ConcurrentHashMap<>(); + + public static void addClient(String sid, CsWsServer csWsServer) { + // 如果存在就先删除一个, 防止重复推送消息 + removeClient(sid); + clientMap.put(sid, csWsServer); + } + + public static void removeClient(String sid) { + CsWsServer wsServer = clientMap.get(sid); + if (wsServer != null) { + try { + // log.info("关闭session, sid={}", sid); + wsServer.getSession().close(); + } catch (Exception e) { + log.error("Close session failed, sid={}", sid, e); + } + // log.info("停止任务, sid={}", sid); + wsServer.stopTask(sid); + // log.info("移除客户端, sid={}", sid); + clientMap.remove(sid); + } + } + + public static Collection getAllClient() { + return clientMap.values(); + } + + public static CsWsServer getClientBySid(String sid) { + return clientMap.get(sid); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/CsWsMessage.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/CsWsMessage.java new file mode 100644 index 00000000..f9b91d6e --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/CsWsMessage.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.websocket.context; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CsWsMessage extends CsObject { + private String bizCode; + private String data; + + public CsWsMessage(String bizCode, String data) { + this.bizCode = bizCode; + this.data = data; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/CsWsServer.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/CsWsServer.java new file mode 100644 index 00000000..7f2a81a3 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/CsWsServer.java @@ -0,0 +1,175 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.websocket.context; + +import com.alibaba.fastjson2.JSON; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.websocket.*; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +@ServerEndpoint("/websocket/{sid}") +@Getter +public class CsWsServer { + private static final Map taskThreadMap = new ConcurrentHashMap<>(); + /** + * 与某个客户端的连接会话, 需要通过它来给客户端发送数据 + */ + private Session session; + /** + * 接收sid + */ + private String sid = ""; + + /** + * 连接建立成功调用的方法 + */ + @OnOpen + public void onOpen(Session session, @PathParam("sid") String sid) { + this.session = session; + // {username}#{bizCode}#{contextParams} + this.sid = sid; + CsWsClientManager.addClient(sid, this); + } + + /** + * 收到客户端消息后调用的方法 + * + * @param message 客户端发送过来的消息 + */ + @OnMessage + public void onMessage(String message, Session session) { + CsWsMessage wsMessage = JSON.parseObject(message, CsWsMessage.class); + String bizCode = wsMessage.getBizCode(); + Object data = wsMessage.getData(); + log.info("收到来 sid={} 的信息: message={}, bizCode={}, data={}", sid, message, bizCode, JSON.toJSONString(data)); + } + + /** + * 连接关闭调用的方法 + */ + @OnClose + public void onClose() { + try { + CsWsClientManager.removeClient(this.sid); + } catch (Exception e) { + log.error("WebSocket onClose error, sid={}", this.sid, e); + } + } + + @OnError + public void onError(Session session, Throwable error) { + // log.error("WebSocket sid={} 发生错误", this.sid, error); + try { + CsWsClientManager.removeClient(this.sid); + } catch (Exception e) { + log.error("WebSocket onClose error, sid={}", this.sid, e); + } + } + + /** + * 实现服务器主动推送 + */ + private void innerSendMessage(String message) throws IOException { + this.session.getBasicRemote().sendText(message); + } + + /** + * 精准推送消息 + */ + public void sendMessage(CsWsMessage message, @PathParam("sid") String sid) throws IOException { + String body = JSON.toJSONString(message); + // log.info("推送消息到{}, 推送内容:{}", sid, message); + try { + if (sid == null) { + sendToAll(body); + } else { + CsWsServer wsClient = CsWsClientManager.getClientBySid(sid); + if (wsClient != null) { + wsClient.innerSendMessage(body); + } + } + } catch (Exception ignored) { + } + } + + /** + * 群发消息 + */ + public void sendToAll(String message) { + for (CsWsServer item : CsWsClientManager.getAllClient()) { + try { + item.innerSendMessage(message); + } catch (IOException e) { + log.error("发送消息给 sid={} 失败", item.sid, e); + CsWsClientManager.removeClient(item.sid); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CsWsServer that = (CsWsServer) o; + return Objects.equals(session, that.session) && Objects.equals(sid, that.sid); + } + + @Override + public int hashCode() { + return Objects.hash(session, sid); + } + + public Thread getTaskThread(String sid) { + Thread thread = taskThreadMap.get(sid); + if (thread == null) { + return null; + } + if (thread.isInterrupted()) { + taskThreadMap.remove(sid); + return null; + } + return thread; + } + + public void restartTask(Runnable runnable) { + stopTask(this.sid); + Thread thread = new Thread(runnable); + thread.start(); + taskThreadMap.put(this.sid, thread); + } + + public void stopTask(String sid) { + Thread thread = getTaskThread(this.sid); + if (thread != null) { + thread.stop(); + } + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/model/WsSidVo.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/model/WsSidVo.java new file mode 100644 index 00000000..8b7cd5e1 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/model/WsSidVo.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.websocket.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class WsSidVo extends CsObject { + private String username; + private String bizCode; + private String param; +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/util/WsMessageUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/util/WsMessageUtil.java new file mode 100644 index 00000000..dff4aeda --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/framework/websocket/util/WsMessageUtil.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.framework.websocket.util; + +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.websocket.model.WsSidVo; +import lombok.experimental.UtilityClass; + +/** + * WebSocket消息解析工具 + * + * @author odboy + * @date 2025-07-25 + */ +@UtilityClass +public class WsMessageUtil { + public static WsSidVo parseSid(String sid) { + // sid {username}_{bizCode}_{param} + String[] sids = sid.split("_"); + if (sids.length != 3) { + throw new BadRequestException("sid格式异常"); + } + WsSidVo sidVo = new WsSidVo(); + sidVo.setUsername(sids[0]); + sidVo.setBizCode(sids[1]); + sidVo.setParam(sids[2]); + return sidVo; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsAnonTagUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsAnonTagUtil.java new file mode 100644 index 00000000..fc11de37 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsAnonTagUtil.java @@ -0,0 +1,112 @@ + +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import cn.odboy.annotation.AnonymousAccess; +import cn.odboy.constant.RequestMethodEnum; +import lombok.experimental.UtilityClass; +import org.springframework.context.ApplicationContext; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.util.*; + +/** + * 匿名标记工具 + */ +@UtilityClass +public class CsAnonTagUtil { + + /** + * 获取匿名标记的URL + * + * @param applicationContext / + * @return / + */ + public static Map> getAnonymousUrl(final ApplicationContext applicationContext) { + RequestMappingHandlerMapping requestMappingHandlerMapping = + (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping"); + Map handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods(); + Map> anonymousUrls = new HashMap<>(8); + // 获取匿名标记 + Set get = new HashSet<>(); + Set post = new HashSet<>(); + Set put = new HashSet<>(); + Set patch = new HashSet<>(); + Set delete = new HashSet<>(); + Set all = new HashSet<>(); + for (Map.Entry infoEntry : handlerMethodMap.entrySet()) { + HandlerMethod handlerMethod = infoEntry.getValue(); + AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class); + if (null != anonymousAccess && infoEntry.getKey() != null) { + List requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods()); + RequestMethodEnum request = RequestMethodEnum.find(requestMethods.isEmpty() ? + RequestMethodEnum.ALL.getType() : + requestMethods.get(0).name()); + PatternsRequestCondition patternsCondition = infoEntry.getKey().getPatternsCondition(); + if (patternsCondition != null) { + switch (Objects.requireNonNull(request)) { + case GET: + get.addAll(patternsCondition.getPatterns()); + break; + case POST: + post.addAll(patternsCondition.getPatterns()); + break; + case PUT: + put.addAll(patternsCondition.getPatterns()); + break; + case PATCH: + patch.addAll(patternsCondition.getPatterns()); + break; + case DELETE: + delete.addAll(patternsCondition.getPatterns()); + break; + default: + all.addAll(patternsCondition.getPatterns()); + break; + } + } + } + } + anonymousUrls.put(RequestMethodEnum.GET.getType(), get); + anonymousUrls.put(RequestMethodEnum.POST.getType(), post); + anonymousUrls.put(RequestMethodEnum.PUT.getType(), put); + anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch); + anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete); + anonymousUrls.put(RequestMethodEnum.ALL.getType(), all); + return anonymousUrls; + } + + /** + * 获取所有匿名标记的URL + * + * @param applicationContext / + * @return / + */ + public static Set getAllAnonymousUrl(final ApplicationContext applicationContext) { + Set allUrl = new HashSet<>(); + Map> anonymousUrls = getAnonymousUrl(applicationContext); + for (Map.Entry> entry : anonymousUrls.entrySet()) { + allUrl.addAll(anonymousUrls.get(entry.getKey())); + } + return allUrl; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsBase64SecretUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsBase64SecretUtil.java new file mode 100644 index 00000000..2ab653f2 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsBase64SecretUtil.java @@ -0,0 +1,50 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import cn.hutool.core.codec.Base64Encoder; +import cn.hutool.core.util.IdUtil; +import lombok.experimental.UtilityClass; + +import java.nio.charset.StandardCharsets; + +/** + * 加密令牌生成工具 + * + * @author odboy + * @date 2022-11-16 + */ +@UtilityClass +public final class CsBase64SecretUtil { + + public static String build() { + // 128位uuid + return Base64Encoder.encode(getLongUuid(4).getBytes(StandardCharsets.UTF_8)); + } + + public static String getLongUuid(int bit) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < bit; i++) { + result.append(IdUtil.simpleUUID()); + } + return result.toString(); + } + + public static void main(String[] args) { + System.out.println(build()); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsBigDecimalUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsBigDecimalUtil.java new file mode 100644 index 00000000..2f585c88 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsBigDecimalUtil.java @@ -0,0 +1,154 @@ + +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import lombok.experimental.UtilityClass; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 计算类 + */ +@UtilityClass +public final class CsBigDecimalUtil { + + /** + * 将对象转换为 BigDecimal + * + * @param obj 输入对象 + * @return 转换后的 BigDecimal + */ + private static BigDecimal toBigDecimal(Object obj) { + if (obj instanceof BigDecimal) { + return (BigDecimal) obj; + } else if (obj instanceof Long) { + return BigDecimal.valueOf((Long) obj); + } else if (obj instanceof Integer) { + return BigDecimal.valueOf((Integer) obj); + } else if (obj instanceof Double) { + return new BigDecimal(String.valueOf(obj)); + } else { + throw new IllegalArgumentException("Unsupported type"); + } + } + + /** + * 加法 + * + * @param a 加数 + * @param b 加数 + * @return 两个加数的和, 保留两位小数 + */ + public static BigDecimal add(Object a, Object b) { + BigDecimal bdA = toBigDecimal(a); + BigDecimal bdB = toBigDecimal(b); + return bdA.add(bdB).setScale(2, RoundingMode.HALF_UP); + } + + /** + * 减法 + * + * @param a 被减数 + * @param b 减数 + * @return 两数的差, 保留两位小数 + */ + public static BigDecimal subtract(Object a, Object b) { + BigDecimal bdA = toBigDecimal(a); + BigDecimal bdB = toBigDecimal(b); + return bdA.subtract(bdB).setScale(2, RoundingMode.HALF_UP); + } + + /** + * 乘法 + * + * @param a 乘数 + * @param b 乘数 + * @return 两个乘数的积, 保留两位小数 + */ + public static BigDecimal multiply(Object a, Object b) { + BigDecimal bdA = toBigDecimal(a); + BigDecimal bdB = toBigDecimal(b); + return bdA.multiply(bdB).setScale(2, RoundingMode.HALF_UP); + } + + /** + * 除法 + * + * @param a 被除数 + * @param b 除数 + * @return 两数的商,保留两位小数 + */ + public static BigDecimal divide(Object a, Object b) { + BigDecimal bdA = toBigDecimal(a); + BigDecimal bdB = toBigDecimal(b); + return bdA.divide(bdB, 2, RoundingMode.HALF_UP); + } + + /** + * 除法 + * + * @param a 被除数 + * @param b 除数 + * @param scale 保留小数位数 + * @return 两数的商,保留两位小数 + */ + public static BigDecimal divide(Object a, Object b, int scale) { + BigDecimal bdA = toBigDecimal(a); + BigDecimal bdB = toBigDecimal(b); + return bdA.divide(bdB, scale, RoundingMode.HALF_UP); + } + + /** + * 分转元 + * + * @param obj 分的金额 + * @return 转换后的元,保留两位小数 + */ + public static BigDecimal centsToYuan(Object obj) { + BigDecimal cents = toBigDecimal(obj); + return cents.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); + } + + /** + * 元转分 + * + * @param obj 元的金额 + * @return 转换后的分 + */ + public static Long yuanToCents(Object obj) { + BigDecimal yuan = toBigDecimal(obj); + return yuan.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).longValue(); + } + + public static void main(String[] args) { + BigDecimal num1 = new BigDecimal("10.123"); + BigDecimal num2 = new BigDecimal("2.456"); + + System.out.println("加法结果: " + add(num1, num2)); + System.out.println("减法结果: " + subtract(num1, num2)); + System.out.println("乘法结果: " + multiply(num1, num2)); + System.out.println("除法结果: " + divide(num1, num2)); + + Long cents = 12345L; + System.out.println("分转元结果: " + centsToYuan(cents)); + + BigDecimal yuan = new BigDecimal("123.45"); + System.out.println("元转分结果: " + yuanToCents(yuan)); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsBrowserUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsBrowserUtil.java new file mode 100644 index 00000000..5cb43432 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsBrowserUtil.java @@ -0,0 +1,74 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletRequest; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * 浏览器 相关 + */ +@Slf4j +@UtilityClass +public final class CsBrowserUtil { + private static final String UNKNOWN = "unknown"; + + /** + * 获取ip地址 + */ + public static String getIp(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + String comma = ","; + String localhost = "127.0.0.1"; + if (ip.contains(comma)) { + ip = ip.split(",")[0]; + } + if (localhost.equals(ip)) { + // 获取本机真正的ip地址 + try { + ip = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + log.error("获取本机地址失败", e); + } + } + return ip; + } + + /** + * 获取浏览器 + */ + public static String getVersion(HttpServletRequest request) { + UserAgent ua = UserAgentUtil.parse(request.getHeader("User-Agent")); + String browser = ua.getBrowser().toString() + " " + ua.getVersion(); + return browser.replace(".0.0.0", ""); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsClassUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsClassUtil.java new file mode 100644 index 00000000..a9d9e060 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsClassUtil.java @@ -0,0 +1,39 @@ + +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import lombok.experimental.UtilityClass; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; + +/** + * 类 相关 + */ +@UtilityClass +public final class CsClassUtil { + @SuppressWarnings({"unchecked", "all"}) + public static List getAllFields(Class clazz, List fields) { + if (clazz != null) { + fields.addAll(Arrays.asList(clazz.getDeclaredFields())); + getAllFields(clazz.getSuperclass(), fields); + } + return fields; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsCloseUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsCloseUtil.java new file mode 100644 index 00000000..b5cd78b0 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsCloseUtil.java @@ -0,0 +1,49 @@ + +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import lombok.experimental.UtilityClass; + +import java.io.Closeable; + +/** + * 用于关闭各种连接, 缺啥补啥 + */ +@UtilityClass +public final class CsCloseUtil { + + public static void close(Closeable closeable) { + if (null != closeable) { + try { + closeable.close(); + } catch (Exception e) { + // 静默关闭 + } + } + } + + public static void close(AutoCloseable closeable) { + if (null != closeable) { + try { + closeable.close(); + } catch (Exception e) { + // 静默关闭 + } + } + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsCollUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsCollUtil.java new file mode 100644 index 00000000..1b9f9ca1 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsCollUtil.java @@ -0,0 +1,224 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import cn.odboy.base.CsSelectOptionVo; +import com.alibaba.fastjson2.JSON; +import lombok.experimental.UtilityClass; + +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * 集合工具 + * + * @author odboy + * @date 2022-11-09 + */ +@UtilityClass +public final class CsCollUtil extends cn.hutool.core.collection.CollUtil { + private static Predicate distinctPredicate(Function keyMapper) { + Map map = new HashMap<>(3); + return (t) -> null == map.putIfAbsent(keyMapper.apply(t), true); + } + + /** + * 流根据对象属性去重 + *

+ * 使用方式: list = CollUtil.streamDistinct(list, Object::getXXX) + */ + public static List streamDistinct(List data, Function keyMapper) { + return data.stream().filter(distinctPredicate(keyMapper)).collect(Collectors.toList()); + } + + /** + * 流根据对象属性自然排序 + *

+ * 使用方式: list = CollUtil.streamAscSort(list, Object::getXXX) + * + * @param data / + * @param keyExtractor / + * @param / + * @param / + * @return / + */ + public static > List streamAscSort( + List data, + Function keyExtractor) { + return data.stream().sorted(Comparator.comparing(keyExtractor)).collect(Collectors.toList()); + } + + /** + * 流根据对象属性倒转排序 + *

+ * 使用方式: list = CollUtil.streamDescSort(list, Object::getXXX) + * + * @param data / + * @param keyExtractor / + * @param / + * @param / + * @return / + */ + public static > List streamDescSort( + List data, + Function keyExtractor) { + return data.stream().sorted(Comparator.comparing(keyExtractor).reversed()).collect(Collectors.toList()); + } + + /** + * 流拆分对象为键值对 + *

+ * 使用方式: map = CollUtil.streamToMap(list, Object::getXXKey, Object::getXXValue) + * + * @param data / + * @param keyMapper / + * @param valueMapper / + * @param / + * @param / + * @param / + * @return / + */ + public static Map streamToMap( + List data, + Function keyMapper, + Function valueMapper) { + // 根据key去重再并流 + return streamDistinct(data, keyMapper).stream().collect(Collectors.toMap(keyMapper, valueMapper)); + } + + /** + * 流对List求和 + *

+ * 使用方式: total = CollUtil.streamDoubleAdd(doubleList) + * + * @param data / + * @return / + */ + public static Double streamDoubleAdd(List data) { + if (CsCollUtil.isNotEmpty(data)) { + return data.stream().filter(Objects::nonNull).reduce(Double::sum).orElse(0.0); + } + return 0.0; + } + + /** + * 流对List求和 + *

+ * 使用方式: total = CollUtil.streamBigDecimalAdd(doubleList) + * + * @param data / + * @return / + */ + public static BigDecimal streamBigDecimalAdd(List data) { + if (CsCollUtil.isNotEmpty(data)) { + return data.stream().filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO); + } + return BigDecimal.ZERO; + } + + /** + * 流对List中的某一BigDecimal属性求和 + *

+ * 使用方式: total = CollUtil.streamBigDecimalAdd(list, XXX) + * + * @param data / + * @return / + */ + public static BigDecimal streamBigDecimalAdd(List data, String elementName) { + return data.stream().filter(Objects::nonNull).map(m -> { + // 反射, 取集合中各元素的某属性 + BigDecimal that; + try { + Field field = m.getClass().getDeclaredField(elementName); + field.setAccessible(true); + that = (BigDecimal) field.get(m); + } catch (Exception e) { + // 忽略异常 + that = BigDecimal.ZERO; + } + return that; + }).reduce(BigDecimal::add).orElse(BigDecimal.ZERO); + } + + /** + * 流对List取最大值 + *

+ * 使用方式: max = CollUtil.streamBigDecimalMax(list) + * + * @param data / + * @return / + */ + public static BigDecimal streamBigDecimalMax(List data) { + return data.stream().filter(Objects::nonNull).reduce(BigDecimal::max).orElse(null); + } + + /** + * 流对List取最小值 + *

+ * 使用方式: max = CollUtil.streamBigDecimalMin(list) + * + * @param data / + * @return / + */ + public static BigDecimal streamBigDecimalMin(List data) { + return data.stream().filter(Objects::nonNull).reduce(BigDecimal::min).orElse(null); + } + + /** + * 流对List取平均值 + *

+ * 使用方式: avg = CollUtil.streamBigDecimalAvg(list) + * + * @param data / + * @return / + */ + public static BigDecimal streamBigDecimalAvg(List data) { + return data.stream() + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add) + .divide(BigDecimal.valueOf(data.size()), 6, RoundingMode.FLOOR); + } + + public static void main(String[] args) { + List testData = new ArrayList<>(); + CsSelectOptionVo selectOption1 = CsSelectOptionVo.builder().label("苹果").value("apple").build(); + CsSelectOptionVo selectOption2 = CsSelectOptionVo.builder().label("苹果").value("apple").build(); + CsSelectOptionVo selectOption3 = CsSelectOptionVo.builder().label("香蕉").value("banana").build(); + CsSelectOptionVo selectOption4 = CsSelectOptionVo.builder().label("猫").value("cat").build(); + testData.add(selectOption1); + testData.add(selectOption2); + testData.add(selectOption3); + testData.add(selectOption4); + List selectOptions1 = CsCollUtil.streamAscSort(testData, CsSelectOptionVo::getValue); + System.out.println(JSON.toJSONString(selectOptions1)); + List selectOptions2 = CsCollUtil.streamDescSort(testData, CsSelectOptionVo::getValue); + System.out.println(JSON.toJSONString(selectOptions2)); + List selectOptions3 = CsCollUtil.streamDistinct(testData, CsSelectOptionVo::getValue); + System.out.println(JSON.toJSONString(selectOptions3)); + Map selectMap1 = + CsCollUtil.streamToMap(testData, CsSelectOptionVo::getValue, CsSelectOptionVo::getLabel); + System.out.println(JSON.toJSONString(selectMap1)); + // 这只是一个示范 + BigDecimal total1 = CsCollUtil.streamBigDecimalAdd(testData, "value"); + System.out.println(total1); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsDateUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsDateUtil.java new file mode 100644 index 00000000..b2c98f4c --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsDateUtil.java @@ -0,0 +1,150 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import lombok.experimental.UtilityClass; + +import java.util.Date; + +/** + * 时间相关工具 + * + * @author odboy + * @date 2025-10-01 + */ +@UtilityClass +public class CsDateUtil { + /** + * 获取当前时间毫秒数 + * + * @return / + */ + public static String getNowDateTimeMsStr() { + return DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN); + } + + /** + * 获取当前时间,但不包括毫秒数 + * + * @return / + */ + public static String getNowDateTimeStr() { + return DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN); + } + + /** + * 获取当前时间,但不包括毫秒数 + * + * @return / + */ + public static String getNowDateStr() { + return DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN); + } + + /** + * 格式化到毫秒 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 格式化后的时间,X天X时X分X秒X毫秒 + */ + public static String formatMillsDuration(Date startTime, Date endTime) { + if (startTime == null || endTime == null) { + // 什么都不返回 + return ""; + } + + long betweenSeconds = DateUtil.between(startTime, endTime, cn.hutool.core.date.DateUnit.SECOND); + + long days = betweenSeconds / (24 * 60 * 60); + long hours = (betweenSeconds % (24 * 60 * 60)) / (60 * 60); + long minutes = (betweenSeconds % (60 * 60)) / 60; + long seconds = betweenSeconds % 60; + + StringBuilder result = new StringBuilder(); + + if (days > 0) { + result.append(days).append("天"); + } + + if (hours > 0) { + result.append(hours).append("时"); + } + + if (minutes > 0) { + result.append(minutes).append("分"); + } + + if (seconds > 0 || result.length() == 0) { + result.append(seconds).append("秒"); + } + + return result.toString(); + } + + /** + * 格式化到秒 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 格式化后的时间,X天X时X分X秒 + */ + public static String formatSecondsDuration(Date startTime, Date endTime) { + if (startTime == null || endTime == null) { + // 什么都不返回 + return ""; + } + + long betweenMs = DateUtil.betweenMs(startTime, endTime); + long days = betweenMs / (24 * 60 * 60 * 1000); + long hours = (betweenMs % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000); + long minutes = (betweenMs % (60 * 60 * 1000)) / (60 * 1000); + long seconds = (betweenMs % (60 * 1000)) / 1000; + long milliseconds = betweenMs % 1000; + + StringBuilder result = new StringBuilder(); + + if (days > 0) { + result.append(days).append("天"); + } + + if (hours > 0) { + result.append(hours).append("时"); + } + + if (minutes > 0) { + result.append(minutes).append("分"); + } + + if (seconds > 0) { + result.append(seconds).append("秒"); + } + + if (milliseconds > 0) { + result.append(milliseconds).append("毫秒"); + } + + // 如果所有单位都是0,则显示0毫秒 + if (result.length() == 0) { + result.append("0毫秒"); + } + + return result.toString(); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsDesEncryptUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsDesEncryptUtil.java new file mode 100644 index 00000000..3cd38ff2 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsDesEncryptUtil.java @@ -0,0 +1,97 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import lombok.experimental.UtilityClass; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.IvParameterSpec; +import java.nio.charset.StandardCharsets; + +/** + * DES加密 + */ +@UtilityClass +public final class CsDesEncryptUtil { + + private static final String STR_PARAM = "Passw0rd"; + private static final IvParameterSpec IV = new IvParameterSpec(STR_PARAM.getBytes(StandardCharsets.UTF_8)); + + private static DESKeySpec getDesKeySpec(String source) throws Exception { + if (source == null || source.isEmpty()) { + return null; + } + return new DESKeySpec(STR_PARAM.getBytes(StandardCharsets.UTF_8)); + } + + /** + * 对称加密 + */ + public static String desEncrypt(String source) throws Exception { + Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); + DESKeySpec desKeySpec = getDesKeySpec(source); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + SecretKey secretKey = keyFactory.generateSecret(desKeySpec); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV); + return byte2hex(cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase(); + } + + /** + * 对称解密 + */ + public static String desDecrypt(String source) throws Exception { + Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); + byte[] src = hex2byte(source.getBytes(StandardCharsets.UTF_8)); + DESKeySpec desKeySpec = getDesKeySpec(source); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + SecretKey secretKey = keyFactory.generateSecret(desKeySpec); + cipher.init(Cipher.DECRYPT_MODE, secretKey, IV); + byte[] retByte = cipher.doFinal(src); + return new String(retByte); + } + + private static String byte2hex(byte[] inStr) { + String stmp; + StringBuilder out = new StringBuilder(inStr.length * 2); + for (byte b : inStr) { + stmp = Integer.toHexString(b & 0xFF); + if (stmp.length() == 1) { + out.append("0").append(stmp); + } else { + out.append(stmp); + } + } + return out.toString(); + } + + private static byte[] hex2byte(byte[] b) { + int size = 2; + if ((b.length % size) != 0) { + throw new IllegalArgumentException("长度不是偶数"); + } + byte[] b2 = new byte[b.length / 2]; + for (int n = 0; n < b.length; n += size) { + String item = new String(b, n, 2); + b2[n / 2] = (byte) Integer.parseInt(item, 16); + } + return b2; + } +} + diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsDistributedIdUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsDistributedIdUtil.java new file mode 100644 index 00000000..3b9318c5 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsDistributedIdUtil.java @@ -0,0 +1,206 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import cn.odboy.framework.exception.BadRequestException; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * 分布式id生成工具(不建议用,仅供参考) + * + * @author odboy + * @date 2025-04-05 + */ +public final class CsDistributedIdUtil { + /** + * 起始毫秒时间戳, 2020-12-30 + */ + private static final long START_TIMESTAMP = 1609314517000L; + /** + * 数据中心 ID 所占位数 + */ + private final long dataCenterIdBits; + /** + * 机器 ID 所占位数 + */ + private final long workerIdBits; + /** + * 序列号所占位数 + */ + private final long sequenceBits; + + /** + * 数据中心 ID 最大值 + */ + private final long maxDataCenterId; + /** + * 机器 ID 最大值 + */ + private final long maxWorkerId; + /** + * 序列号最大值 + */ + private final long sequenceMask; + + /** + * 机器 ID 向左移位数 + */ + private final long workerIdShift; + /** + * 数据中心 ID 向左移位数 + */ + private final long dataCenterIdShift; + /** + * 时间戳向左移位数 + */ + private final long timestampLeftShift; + + /** + * 数据中心 ID + */ + private final long dataCenterId; + /** + * 机器 ID + */ + private final long workerId; + /** + * 序列号 + */ + private final AtomicLong sequence = new AtomicLong(0L); + /** + * 上一次生成 ID 的时间戳 + */ + private final AtomicLong lastTimestamp = new AtomicLong(-1L); + + /** + * 单例实例持有者类 + */ + private static class InstanceHolder { + private static final CsDistributedIdUtil INSTANCE = new CsDistributedIdUtil(20, 12, 5, 5, 12); + } + + /** + * 构造函数, 初始化数据中心 ID 和机器 ID + * + * @param dataCenterId 数据中心 ID + * @param workerId 机器 ID + * @param dataCenterIdBits 数据中心 ID 所占位数 + * @param workerIdBits 机器 ID 所占位数 + * @param sequenceBits 序列号所占位数 + */ + public CsDistributedIdUtil( + long dataCenterId, + long workerId, + long dataCenterIdBits, + long workerIdBits, + long sequenceBits) { + this.dataCenterIdBits = dataCenterIdBits; + this.workerIdBits = workerIdBits; + this.sequenceBits = sequenceBits; + + this.maxDataCenterId = ~(-1L << dataCenterIdBits); + this.maxWorkerId = ~(-1L << workerIdBits); + this.sequenceMask = ~(-1L << sequenceBits); + + this.workerIdShift = sequenceBits; + this.dataCenterIdShift = sequenceBits + workerIdBits; + this.timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits; + + if (dataCenterId > maxDataCenterId || dataCenterId < 0) { + throw new IllegalArgumentException("Data center ID can't be greater than " + maxDataCenterId + " or less than 0"); + } + if (workerId > maxWorkerId || workerId < 0) { + throw new IllegalArgumentException("Worker ID can't be greater than " + maxWorkerId + " or less than 0"); + } + this.dataCenterId = dataCenterId; + this.workerId = workerId; + } + + /** + * 生成下一个分布式 ID + * + * @return 生成的分布式 ID + */ + public long nextId() { + long currentTimestamp = System.currentTimeMillis(); + + // 如果当前时间戳小于上一次生成 ID 的时间戳, 说明时钟回拨,抛出异常 + if (currentTimestamp < lastTimestamp.get()) { + throw new BadRequestException( + "Clock moved backwards. Refusing to generate id for " + (lastTimestamp.get() - currentTimestamp) + + " milliseconds"); + } + + long lastTimestampValue = lastTimestamp.get(); + if (currentTimestamp == lastTimestampValue) { + // 同一毫秒内,序列号自增 + long seq = sequence.incrementAndGet() & sequenceMask; + if (seq == 0) { + // 序列号溢出,等待下一毫秒 + currentTimestamp = waitNextMillis(lastTimestampValue); + } + return ((currentTimestamp - START_TIMESTAMP) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) | + (workerId << workerIdShift) | seq; + } else { + // 时间戳改变,重置序列号 + sequence.set(0L); + lastTimestamp.set(currentTimestamp); + return ((currentTimestamp - START_TIMESTAMP) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) | + (workerId << workerIdShift); + } + } + + /** + * 等待下一毫秒 + * + * @param lastTimestamp 上一次生成 ID 的时间戳 + * @return 当前时间戳 + */ + private long waitNextMillis(long lastTimestamp) { + long timestamp = System.currentTimeMillis(); + while (timestamp <= lastTimestamp) { + timestamp = System.currentTimeMillis(); + } + return timestamp; + } + + /** + * 获取单例实例的下一个分布式 ID + * + * @return 生成的分布式 ID + */ + public static long fastNextId() { + return InstanceHolder.INSTANCE.nextId(); + } + + /** + * 主方法,用于测试分布式 ID 生成 + * + * @param args 命令行参数 + */ + public static void main(String[] args) { + for (int i = 0; i < 10; i++) { + new Thread(() -> { + long id = Thread.currentThread().getId(); + for (int j = 0; j < 20; j++) { + System.out.println(id + " - " + CsDistributedIdUtil.fastNextId()); + } + }).start(); + } + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsFileUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsFileUtil.java new file mode 100644 index 00000000..42137619 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsFileUtil.java @@ -0,0 +1,398 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.poi.excel.BigExcelWriter; +import cn.hutool.poi.excel.ExcelUtil; +import cn.odboy.constant.FileTypeEnum; +import cn.odboy.constant.SystemConst; +import cn.odboy.framework.exception.BadRequestException; +import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.util.IOUtils; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.nio.file.Files; +import java.security.MessageDigest; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static cn.odboy.constant.SystemConst.*; + +/** + * File工具类, 扩展 hutool 工具包 + */ +@Slf4j +@UtilityClass +public final class CsFileUtil extends cn.hutool.core.io.FileUtil { + + /** + * 系统临时目录 + *
+ * windows 包含路径分割符, 但Linux 不包含, 在windows \\==\ 前提下, 为安全起见 同意拼装 路径分割符, + *

+     *       java.io.tmpdir
+     *       windows : C:\Users/xxx\AppData\Local\Temp\
+     *       linux: /temp
+     * 
+ */ + public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator; + /** + * 定义GB的计算常量 + */ + private static final int GB = 1024 * 1024 * 1024; + /** + * 定义MB的计算常量 + */ + private static final int MB = 1024 * 1024; + /** + * 定义KB的计算常量 + */ + private static final int KB = 1024; + + /** + * 格式化小数 + */ + private static final DecimalFormat DF = new DecimalFormat("0.00"); + + /** + * MultipartFile转File + */ + public static File toFile(MultipartFile multipartFile) { + // 获取文件名 + String fileName = multipartFile.getOriginalFilename(); + // 获取文件后缀 + String prefix = "." + getSuffix(fileName); + File file = null; + try { + // 用uuid作为文件名, 防止生成的临时文件重复 + file = new File(SYS_TEM_DIR + IdUtil.simpleUUID() + prefix); + // MultipartFile to File + multipartFile.transferTo(file); + } catch (IOException e) { + log.error("保存临时文件失败", e); + } + return file; + } + + /** + * 文件大小转换 + */ + public static String getSize(long size) { + String resultSize; + if (size / GB >= 1) { + //如果当前Byte的值大于等于1GB + resultSize = DF.format(size / (float) GB) + " GB"; + } else if (size / MB >= 1) { + //如果当前Byte的值大于等于1MB + resultSize = DF.format(size / (float) MB) + " MB"; + } else if (size / KB >= 1) { + //如果当前Byte的值大于等于1KB + resultSize = DF.format(size / (float) KB) + " KB"; + } else { + resultSize = size + " B"; + } + return resultSize; + } + + /** + * inputStream 转 File + */ + public static File inputStreamToFile(InputStream ins, String name) { + File file = new File(SYS_TEM_DIR + name); + if (file.exists()) { + return file; + } + OutputStream os = null; + try { + os = Files.newOutputStream(file.toPath()); + int bytesRead; + int len = 8192; + byte[] buffer = new byte[len]; + while ((bytesRead = ins.read(buffer, 0, len)) != -1) { + os.write(buffer, 0, bytesRead); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + CsCloseUtil.close(os); + CsCloseUtil.close(ins); + } + return file; + } + + /** + * 将文件名解析成文件的上传路径 + */ + public static File upload(MultipartFile file, String filePath) { + Date date = new Date(); + SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmssS"); + // 过滤非法文件名 + String name = getPrefix(verifyFilename(file.getOriginalFilename())); + String suffix = getSuffix(file.getOriginalFilename()); + String nowStr = "-" + format.format(date); + try { + String fileName = name + nowStr + "." + suffix; + String path = filePath + fileName; + // getCanonicalFile 可解析正确各种路径 + File dest = new File(path).getCanonicalFile(); + // 检测是否存在目录 + if (!dest.getParentFile().exists()) { + if (!dest.getParentFile().mkdirs()) { + log.error("创建目录失败, {}", dest.getParentFile().getAbsolutePath()); + } + } + // 文件写入 + file.transferTo(dest); + return dest; + } catch (Exception e) { + log.error("文件上传失败", e); + } + return null; + } + + /** + * 导出excel + */ + public static void downloadExcel(List> list, HttpServletResponse response) throws IOException { + String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx"; + File file = new File(tempPath); + BigExcelWriter writer = ExcelUtil.getBigWriter(file); + // 处理数据以防止CSV注入 + List> sanitizedList = list.parallelStream().map(map -> { + Map sanitizedMap = new LinkedHashMap<>(); + map.forEach((key, value) -> { + if (value instanceof String) { + String strValue = (String) value; + // 检查并处理以特殊字符开头的值 + if (strValue.startsWith(SYMBOL_EQUAL) || strValue.startsWith(SYMBOL_ADD) || + strValue.startsWith(SYMBOL_SUBTRACT) || strValue.startsWith( + SYMBOL_AT)) { + // 添加单引号前缀 + strValue = "'" + strValue; + } + sanitizedMap.put(key, strValue); + } else { + sanitizedMap.put(key, value); + } + }); + return sanitizedMap; + }).collect(Collectors.toList()); + // 一次性写出内容, 使用默认样式,强制输出标题 + writer.write(sanitizedList, true); + SXSSFSheet sheet = (SXSSFSheet) writer.getSheet(); + //上面需要强转SXSSFSheet 不然没有trackAllColumnsForAutoSizing方法 + sheet.trackAllColumnsForAutoSizing(); + //列宽自适应 + writer.autoSizeColumnAll(); + //response为HttpServletResponse对象 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8"); + //test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码 + response.setHeader("Content-Disposition", "attachment;filename=file.xlsx"); + ServletOutputStream out = response.getOutputStream(); + // 终止后删除临时文件 + file.deleteOnExit(); + writer.flush(out, true); + //此处记得关闭输出Servlet流 + IoUtil.close(out); + } + + /** + * 获取文件类型 + */ + public static String getFileType(String type) { + String documents = "txt doc pdf ppt pps xlsx xls docx"; + String voice = "mp3 wav wma mpa ram ra aac aif m4a"; + String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg"; + String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg"; + if (image.contains(type)) { + return FileTypeEnum.IMAGE.getCode(); + } else if (documents.contains(type)) { + return FileTypeEnum.DOC.getCode(); + } else if (voice.contains(type)) { + return FileTypeEnum.VOICE.getCode(); + } else if (video.contains(type)) { + return FileTypeEnum.VIDEO.getCode(); + } else { + return FileTypeEnum.OTHER.getCode(); + } + } + + /** + * 检查文件大小是否超出最大值 + */ + public static void checkSize(long maxSize, long size) { + // 1M + int len = 1024 * 1024; + if (size > (maxSize * len)) { + throw new BadRequestException("文件超出规定大小:" + maxSize + "MB"); + } + } + + /** + * 判断两个文件是否相同 + */ + public static boolean check(File file1, File file2) { + String img1Md5 = getMd5(file1); + String img2Md5 = getMd5(file2); + if (img1Md5 != null) { + return img1Md5.equals(img2Md5); + } + return false; + } + + /** + * 判断两个文件是否相同 + */ + public static boolean check(String file1Md5, String file2Md5) { + return file1Md5.equals(file2Md5); + } + + /** + * 获取文件字节数组 + */ + private static byte[] getByte(File file) { + byte[] b = new byte[(int) file.length()]; + InputStream in = null; + try { + in = Files.newInputStream(file.toPath()); + // 忽略read值 + int read = in.read(b); + } catch (Exception e) { + log.error(e.getMessage(), e); + return null; + } finally { + CsCloseUtil.close(in); + } + return b; + } + + /** + * 获取文件MD5值 + */ + private static String getMd5(byte[] bytes) { + // 16进制字符 + char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + try { + MessageDigest mdTemp = MessageDigest.getInstance("MD5"); + mdTemp.update(bytes); + byte[] md = mdTemp.digest(); + int j = md.length; + char[] str = new char[j * 2]; + int k = 0; + // 移位 输出字符串 + for (byte byte0 : md) { + str[k++] = hexDigits[byte0 >>> 4 & 0xf]; + str[k++] = hexDigits[byte0 & 0xf]; + } + return new String(str); + } catch (Exception e) { + log.error("获取文件MD5值失败", e); + } + return null; + } + + /** + * 下载文件 + * + * @param request / + * @param response / + * @param file / + */ + public static void downloadFile( + HttpServletRequest request, + HttpServletResponse response, + File file, + boolean deleteOnExit) { + response.setCharacterEncoding(request.getCharacterEncoding()); + response.setContentType("application/octet-stream"); + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + response.setHeader("Content-Disposition", "attachment; filename=" + file.getName()); + IOUtils.copy(fis, response.getOutputStream()); + response.flushBuffer(); + } catch (Exception e) { + log.error("下载文件失败", e); + } finally { + if (fis != null) { + try { + fis.close(); + if (deleteOnExit) { + file.deleteOnExit(); + } + } catch (IOException e) { + log.error("关闭IO流失败", e); + } + } + } + } + + /** + * 验证并过滤非法的文件名 + * + * @param fileName 文件名 + * @return 文件名 + */ + public static String verifyFilename(String fileName) { + // 过滤掉特殊字符 + fileName = fileName.replaceAll("[\\\\/:*?\"<>|~\\s]", ""); + + // 去掉文件名开头和结尾的空格和点 + fileName = fileName.trim().replaceAll("^[. ]+|[. ]+$", ""); + + // 不允许文件名超过255(在Mac和Linux中)或260(在Windows中)个字符 + int maxFileNameLength = 255; + if (System.getProperty(SystemConst.PROPERTY_OS_NAME).startsWith(SystemConst.OS_NAME_WINDOWS)) { + maxFileNameLength = 260; + } + if (fileName.length() > maxFileNameLength) { + fileName = fileName.substring(0, maxFileNameLength); + } + + // 过滤掉控制字符 + fileName = fileName.replaceAll("[\\p{Cntrl}]", ""); + + // 过滤掉 ".." 路径 + fileName = fileName.replaceAll("\\.{2,}", ""); + + // 去掉文件名开头的 ".." + fileName = fileName.replaceAll("^\\.+/", ""); + + // 保留文件名中最后一个 "." 字符,过滤掉其他 "." + fileName = fileName.replaceAll("^(.*)(\\.[^.]*)$", "$1").replaceAll("\\.", "") + + fileName.replaceAll("^(.*)(\\.[^.]*)$", "$2"); + + return fileName; + } + + public static String getMd5(File file) { + return getMd5(getByte(file)); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsIPUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsIPUtil.java new file mode 100644 index 00000000..d9b23828 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsIPUtil.java @@ -0,0 +1,90 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import cn.odboy.framework.context.CsSpringBeanHolder; +import lombok.experimental.UtilityClass; +import net.dreamlu.mica.ip2region.core.Ip2regionSearcher; +import net.dreamlu.mica.ip2region.core.IpInfo; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; + +/** + * IP地址 相关 + */ +@UtilityClass +public final class CsIPUtil { + /** + * 注入bean + */ + private final static Ip2regionSearcher IP_SEARCHER = CsSpringBeanHolder.getBean(Ip2regionSearcher.class); + + /** + * 根据ip获取详细地址 + */ + public static String getCityInfo(String ip) { + IpInfo ipInfo = IP_SEARCHER.memorySearch(ip); + if (ipInfo != null) { + return ipInfo.getAddress(); + } + return null; + } + + /** + * 获取当前机器的IP + * + * @return / + */ + public static String getLocalIp() { + try { + InetAddress candidateAddress = null; + // 遍历所有的网络接口 + for (Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + interfaces.hasMoreElements(); ) { + NetworkInterface anInterface = interfaces.nextElement(); + // 在所有的接口下再遍历IP + for (Enumeration inetAddresses = anInterface.getInetAddresses(); + inetAddresses.hasMoreElements(); ) { + InetAddress inetAddr = inetAddresses.nextElement(); + // 排除loopback类型地址 + if (!inetAddr.isLoopbackAddress()) { + if (inetAddr.isSiteLocalAddress()) { + // 如果是site-local地址, 就是它了 + return inetAddr.getHostAddress(); + } else if (candidateAddress == null) { + // site-local类型的地址未被发现, 先记录候选地址 + candidateAddress = inetAddr; + } + } + } + } + if (candidateAddress != null) { + return candidateAddress.getHostAddress(); + } + // 如果没有发现 non-loopback地址.只能用最次选的方案 + InetAddress jdkSuppliedAddress = InetAddress.getLocalHost(); + if (jdkSuppliedAddress == null) { + return ""; + } + return jdkSuppliedAddress.getHostAddress(); + } catch (Exception e) { + return ""; + } + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsJsonUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsJsonUtil.java new file mode 100644 index 00000000..e5dbacae --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsJsonUtil.java @@ -0,0 +1,50 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.TypeReference; +import lombok.experimental.UtilityClass; + +import java.util.List; +import java.util.Map; + +/** + * Json工具 + * + * @author odboy + * @date 2024-10-01 + */ +@UtilityClass +public final class CsJsonUtil { + public static Map toMap(String jsonStr, Class kClass, Class vClass) { + return JSON.parseObject(jsonStr, new TypeReference>() { + }); + } + + public static List toList(String jsonStr, Class vClass) { + return JSON.parseObject(jsonStr, new TypeReference>() { + }); + } + + public static void main(String[] args) { + String mapStr = "{\"key1\":\"value1\", \"key2\":\"value2\"}"; + System.err.println(CsJsonUtil.toMap(mapStr, String.class, String.class)); + String listStr = "[\"value1\", \"value2\"]"; + System.err.println(CsJsonUtil.toList(listStr, String.class)); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsListUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsListUtil.java new file mode 100644 index 00000000..d1ae49f6 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsListUtil.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import cn.hutool.core.date.DateTime; +import cn.odboy.base.CsBaseUserTimeTb; +import lombok.experimental.UtilityClass; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * List 工具 + * + * @author odboy + * @date 2024-10-01 + */ +@UtilityClass +public final class CsListUtil { + /** + * 根据对象T的属性去重 + * + * @param items 对象T集合 + * @param classifier 对象T属性表达式, 比如: User::getName + */ + public static List distinctByArgs(List items, Function classifier) { + return items.stream() + .collect(Collectors.groupingBy(classifier)) + .values() + .stream() + .map(monitorItem -> monitorItem.iterator().next()) + .collect(Collectors.toList()); + } + + /** + * list转map, 保留新值 -> Map userMap = items.stream().collect(Collectors.toMap(User::getName, user -> user))
list转map, 保留旧值 -> Map userMap = users.stream().collect(Collectors.toMap(User::getName, user -> user, (existing, replacement) -> existing)); + */ + public static void main(String[] args) { + List entities = new ArrayList<>(); + int total = 50; + for (int i = 0; i < total; i++) { + CsBaseUserTimeTb entity = new CsBaseUserTimeTb(); + if (i % 2 == 0) { + entity.setCreateBy("odboy"); + } else { + entity.setCreateBy("admin"); + } + entity.setUpdateBy("admin"); + entity.setCreateTime(DateTime.now().toTimestamp()); + entity.setUpdateTime(DateTime.now().toTimestamp()); + entities.add(entity); + } + List entities1 = CsListUtil.distinctByArgs(entities, CsBaseUserTimeTb::getCreateBy); + System.err.println(entities1); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsLogFmtUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsLogFmtUtil.java new file mode 100644 index 00000000..ba1321a4 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsLogFmtUtil.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import lombok.experimental.UtilityClass; + +/** + * 适配 logger 占位符 + * + * @author odboy + * @date 2025-01-13 + */ +@UtilityClass +public final class CsLogFmtUtil { + /** + * 替换字符串中的占位符 {} 为传入的参数 + * + * @param message 消息模板 + * @param args 替换占位符的参数 + * @return 替换后的消息 + */ + public static String format(String message, Object... args) { + if (message == null || args == null || args.length == 0) { + return message; + } + // 使用 String.format 替换占位符 + return String.format(message.replace("{}", "%s"), args); + } + + public static void main(String[] args) { + // 示例用法 + String messageTemplate = "获取dingtalk accessToken失败, code={}, message={}"; + String formattedMessage = format(messageTemplate, 400, "Bad Request"); + // 输出: 获取dingtalk accessToken失败, code=400, message=Bad Request + System.out.println(formattedMessage); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsPageUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsPageUtil.java new file mode 100644 index 00000000..2188b8d5 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsPageUtil.java @@ -0,0 +1,91 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import cn.odboy.base.CsPageResult; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.experimental.UtilityClass; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 分页工具 + * + * @author odboy + */ +@UtilityClass +public final class CsPageUtil extends cn.hutool.core.util.PageUtil { + + /** + * List 分页 + */ + public static List softPaging(long page, long size, List list) { + int pageIndex = Math.toIntExact(page - 1); + int fromIndex = Math.toIntExact(pageIndex * size); + int toIndex = Math.toIntExact(pageIndex * size + size); + if (fromIndex > list.size()) { + return Collections.emptyList(); + } else if (toIndex >= list.size()) { + return list.subList(fromIndex, list.size()); + } else { + return list.subList(fromIndex, toIndex); + } + } + + /** + * Page 数据处理 + */ + public static CsPageResult toPage(IPage page) { + return new CsPageResult<>(page.getRecords(), page.getTotal()); + } + + /** + * 自定义分页 + */ + public static CsPageResult toPage(List list) { + return new CsPageResult<>(list, list.size()); + } + + /** + * 返回空数据 + */ + public static CsPageResult emptyListData() { + return new CsPageResult<>(new ArrayList<>(), 0); + } + + /** + * 返回空数据 + */ + public static CsPageResult emptyData() { + return new CsPageResult<>(null, 0); + } + + /** + * 自定义分页 + */ + public static CsPageResult toPage(List list, long totalElements) { + return new CsPageResult<>(list, totalElements); + } + + public static Page getCount(BaseMapper baseMapper) { + return new Page<>(1, baseMapper.selectCount(null)); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsResourceTemplateUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsResourceTemplateUtil.java new file mode 100644 index 00000000..31244f15 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsResourceTemplateUtil.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.template.Template; +import cn.hutool.extra.template.TemplateConfig; +import cn.hutool.extra.template.TemplateEngine; +import cn.hutool.extra.template.TemplateUtil; +import lombok.experimental.UtilityClass; + +/** + * Resources模版工具 + * + * @author odboy + * @date 2025-06-22 + */ +@UtilityClass +public class CsResourceTemplateUtil { + public static String render(String moduleName, String filename, Dict params) { + String path = StrUtil.isBlank(moduleName) ? "template" : "template/" + moduleName; + TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig(path, TemplateConfig.ResourceMode.CLASSPATH)); + Template template = engine.getTemplate(filename); + return template.render(params); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsReturnValueHandleUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsReturnValueHandleUtil.java new file mode 100644 index 00000000..cf9ca0b7 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsReturnValueHandleUtil.java @@ -0,0 +1,73 @@ + +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.reflect.MethodSignature; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * 返回值处理 工具 + * + * @author odboy + * @date 2025-05-08 + */ +public class CsReturnValueHandleUtil { + public static Object getDefaultValue(ProceedingJoinPoint joinPoint) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + // 获取目标方法的返回值类型 + Class returnType = method.getReturnType(); + if (returnType.isArray()) { + return getEmptyArray(returnType); + } else if (List.class.isAssignableFrom(returnType)) { + return Collections.emptyList(); + } else if (Set.class.isAssignableFrom(returnType)) { + return Collections.emptySet(); + } else if (Collection.class.isAssignableFrom(returnType)) { + // 默认返回空 List + return Collections.emptyList(); + } else { + // 普通对象返回 null + return null; + } + } + + /** + * 根据数组类型返回空数组 + */ + private static Object getEmptyArray(Class arrayType) { + if (arrayType == int[].class) { + return new int[0]; + } else if (arrayType == long[].class) { + return new long[0]; + } else if (arrayType == double[].class) { + return new double[0]; + } else if (arrayType == String[].class) { + return new String[0]; + } else { + // 其他引用类型数组 + return java.lang.reflect.Array.newInstance(arrayType.getComponentType(), 0); + } + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsRsaEncryptUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsRsaEncryptUtil.java new file mode 100644 index 00000000..12f2876e --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsRsaEncryptUtil.java @@ -0,0 +1,214 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import lombok.experimental.UtilityClass; +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import java.io.ByteArrayOutputStream; +import java.security.*; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** + * Rsa 工具类, 公钥私钥生成, 加解密 + */ +@UtilityClass +public final class CsRsaEncryptUtil { + + private static final String SRC = "123456"; + + public static void main(String[] args) throws Exception { + System.out.println("\n"); + RsaKeyPair keyPair = generateKeyPair(); + System.out.println("公钥:" + keyPair.getPublicKey()); + System.out.println("私钥:" + keyPair.getPrivateKey()); + System.out.println("\n"); + test1(keyPair); + System.out.println("\n"); + test2(keyPair); + System.out.println("\n"); + } + + /** + * 公钥加密私钥解密 + */ + private static void test1(RsaKeyPair keyPair) throws Exception { + System.out.println("***************** 公钥加密私钥解密开始 *****************"); + String text1 = encryptByPublicKey(keyPair.getPublicKey(), CsRsaEncryptUtil.SRC); + String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1); + System.out.println("加密前:" + CsRsaEncryptUtil.SRC); + System.out.println("加密后:" + text1); + System.out.println("解密后:" + text2); + if (CsRsaEncryptUtil.SRC.equals(text2)) { + System.out.println("解密字符串和原始字符串一致, 解密成功"); + } else { + System.out.println("解密字符串和原始字符串不一致,解密失败"); + } + System.out.println("***************** 公钥加密私钥解密结束 *****************"); + } + + /** + * 私钥加密公钥解密 + * + * @throws Exception / + */ + private static void test2(RsaKeyPair keyPair) throws Exception { + System.out.println("***************** 私钥加密公钥解密开始 *****************"); + String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), CsRsaEncryptUtil.SRC); + String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1); + System.out.println("加密前:" + CsRsaEncryptUtil.SRC); + System.out.println("加密后:" + text1); + System.out.println("解密后:" + text2); + if (CsRsaEncryptUtil.SRC.equals(text2)) { + System.out.println("解密字符串和原始字符串一致,解密成功"); + } else { + System.out.println("解密字符串和原始字符串不一致,解密失败"); + } + System.out.println("***************** 私钥加密公钥解密结束 *****************"); + } + + /** + * 公钥解密 + * + * @param publicKeyText 公钥 + * @param text 待解密的信息 + * @return / + * @throws Exception / + */ + public static String decryptByPublicKey(String publicKeyText, String text) throws Exception { + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, publicKey); + byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text)); + return new String(result); + } + + /** + * 私钥加密 + * + * @param privateKeyText 私钥 + * @param text 待加密的信息 + * @return / + * @throws Exception / + */ + public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes()); + return Base64.encodeBase64String(result); + } + + /** + * 私钥解密 + * + * @param privateKeyText 私钥 + * @param text 待解密的文本 + * @return / + * @throws Exception / + */ + public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text)); + return new String(result); + } + + /** + * 公钥加密 + * + * @param publicKeyText 公钥 + * @param text 待加密的文本 + * @return / + */ + public static String encryptByPublicKey(String publicKeyText, String text) throws Exception { + X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes()); + return Base64.encodeBase64String(result); + } + + private static byte[] doLongerCipherFinal(int opMode, Cipher cipher, byte[] source) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + if (opMode == Cipher.DECRYPT_MODE) { + out.write(cipher.doFinal(source)); + } else { + int offset = 0; + int totalSize = source.length; + while (totalSize - offset > 0) { + int size = Math.min(cipher.getOutputSize(0) - 11, totalSize - offset); + out.write(cipher.doFinal(source, offset, size)); + offset += size; + } + } + out.close(); + return out.toByteArray(); + } + + /** + * 构建RSA密钥对 + * + * @return / + * @throws NoSuchAlgorithmException / + */ + public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(1024); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); + String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded()); + String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded()); + return new RsaKeyPair(publicKeyString, privateKeyString); + } + + /** + * RSA密钥对对象 + */ + public static class RsaKeyPair { + + private final String publicKey; + private final String privateKey; + + public RsaKeyPair(String publicKey, String privateKey) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + public String getPublicKey() { + return publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsStringUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsStringUtil.java new file mode 100644 index 00000000..90cf4502 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsStringUtil.java @@ -0,0 +1,128 @@ + +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; + +import java.util.Calendar; +import java.util.Date; + +/** + * 字符串工具类, 继承org.apache.commons.lang3.StringUtils类 + */ +@Slf4j +@UtilityClass +public final class CsStringUtil { + + private static final char SEPARATOR = '_'; + + /** + * 驼峰命名法工具 + * + * @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world" + */ + public static String toCamelCase(String s) { + if (s == null) { + return null; + } + + s = s.toLowerCase(); + + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + if (c == SEPARATOR) { + upperCase = true; + } else if (upperCase) { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } else { + sb.append(c); + } + } + + return sb.toString(); + } + + /** + * 驼峰命名法工具 + * + * @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world" + */ + public static String toCapitalizeCamelCase(String s) { + if (s == null) { + return null; + } + s = toCamelCase(s); + return s.substring(0, 1).toUpperCase() + s.substring(1); + } + + /** + * 驼峰命名法工具 + * + * @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world" + */ + public static String toUnderScoreCase(String s) { + if (s == null) { + return null; + } + + StringBuilder sb = new StringBuilder(); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + boolean nextUpperCase = true; + + if (i < (s.length() - 1)) { + nextUpperCase = Character.isUpperCase(s.charAt(i + 1)); + } + + if ((i > 0) && Character.isUpperCase(c)) { + if (!upperCase || !nextUpperCase) { + sb.append(SEPARATOR); + } + upperCase = true; + } else { + upperCase = false; + } + + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 获得当天是周几 + */ + public static String getWeekDay() { + // String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + String[] weekDays = {"星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"}; + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + int w = cal.get(Calendar.DAY_OF_WEEK) - 1; + if (w < 0) { + w = 0; + } + return weekDays[w]; + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsSystemUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsSystemUtil.java new file mode 100644 index 00000000..25a5af3a --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsSystemUtil.java @@ -0,0 +1,35 @@ + +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; + +/** + * 系统层相关 + * + * @author odboy + * @date 2025-08-11 + */ +public class CsSystemUtil { + public static int getCpuCount() { + SystemInfo systemInfo = new SystemInfo(); + CentralProcessor processor = systemInfo.getHardware().getProcessor(); + return processor.getPhysicalProcessorCount(); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsValidUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsValidUtil.java new file mode 100644 index 00000000..408ce2ed --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/CsValidUtil.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import cn.odboy.framework.exception.BadRequestException; +import lombok.experimental.UtilityClass; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import javax.validation.groups.Default; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Bean验证工具 + * + * @author odboy + * @date 2025-01-15 + */ +@UtilityClass +public final class CsValidUtil { + private static final Validator VALIDATOR; + + static { + try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) { + VALIDATOR = factory.getValidator(); + } + } + + public static void validate(T object) { + Set> violations = VALIDATOR.validate(object, Default.class); + if (!violations.isEmpty()) { + throw new BadRequestException(violations.stream() + .map(ConstraintViolation::getMessage) + .collect(Collectors.joining(","))); + } + } +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/pom.xml b/cutejava-springboot3/cutejava-module-system/pom.xml new file mode 100644 index 00000000..e10ef109 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/pom.xml @@ -0,0 +1,27 @@ + + + + cutejava + cn.odboy + 1.4.1 + + 4.0.0 + + cutejava-module-system + 核心模块 + + + + cn.odboy + cutejava-framework + 1.4.1 + + + + io.minio + minio + 8.5.17 + + + diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemCaptchaBizEnum.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemCaptchaBizEnum.java new file mode 100644 index 00000000..a1c7663d --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemCaptchaBizEnum.java @@ -0,0 +1,66 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 验证码业务场景 + */ +@Getter +@AllArgsConstructor +public enum SystemCaptchaBizEnum { + + /* 通过手机号码重置邮箱 */ + PHONE_RESET_EMAIL_CODE("10000", "phone_reset_email_code_", "SystemEmailCaptchaTemplate.ftl", "通过手机号码重置邮箱"), + + /* 通过旧邮箱重置邮箱 */ + EMAIL_RESET_EMAIL_CODE("10001", "email_reset_email_code_", "SystemEmailCaptchaTemplate.ftl", "通过旧邮箱重置邮箱"), + + /* 通过手机号码重置密码 */ + PHONE_RESET_PWD_CODE("10002", "phone_reset_pwd_code_", "SystemEmailCaptchaTemplate.ftl", "通过手机号码重置密码"), + + /* 通过邮箱重置密码 */ + EMAIL_RESET_PWD_CODE("10003", "email_reset_pwd_code_", "SystemEmailCaptchaTemplate.ftl", "通过邮箱重置密码"); + + /** + * 业务编码 + */ + private final String bizCode; + /** + * redis中的key + */ + private final String redisKey; + /** + * resources文件夹,template中的模版名称 + */ + private final String templateName; + /** + * 业务描述 + */ + private final String description; + + public static SystemCaptchaBizEnum getByBizCode(String bizCode) { + for (SystemCaptchaBizEnum value : SystemCaptchaBizEnum.values()) { + if (value.getBizCode().equals(bizCode)) { + return value; + } + } + return null; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemDataScopeEnum.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemDataScopeEnum.java new file mode 100644 index 00000000..7983cf0e --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemDataScopeEnum.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 数据权限枚举 + */ +@Getter +@AllArgsConstructor +public enum SystemDataScopeEnum { + + /* 全部的数据权限 */ + ALL("全部", "全部的数据权限"), + + /* 自己部门的数据权限 */ + THIS_LEVEL("本级", "自己部门的数据权限"), + + /* 自定义的数据权限 */ + CUSTOMIZE("自定义", "自定义的数据权限"); + + private final String value; + private final String description; + + public static SystemDataScopeEnum find(String val) { + for (SystemDataScopeEnum dataScopeEnum : SystemDataScopeEnum.values()) { + if (dataScopeEnum.getValue().equals(val)) { + return dataScopeEnum; + } + } + return null; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/constant/TransferProtocolConst.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/constant/TransferProtocolConst.java new file mode 100644 index 00000000..43b37c71 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/constant/TransferProtocolConst.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.constant; + +public interface TransferProtocolConst { + String PREFIX_HTTP = "http://"; + String PREFIX_HTTPS = "https://"; + String PREFIX_HTTPS_BAD_REQUEST = "外链必须以http://或者https://开头"; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemAuthController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemAuthController.java new file mode 100644 index 00000000..59e7e9de --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemAuthController.java @@ -0,0 +1,156 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.annotation.AnonymousPostMapping; +import cn.odboy.constant.CaptchaCodeEnum; +import cn.odboy.constant.SystemConst; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.redis.CsRedisHelper; +import cn.odboy.system.dal.model.SystemUserInfoVo; +import cn.odboy.system.dal.model.SystemUserJwtVo; +import cn.odboy.system.dal.model.SystemUserLoginArgs; +import cn.odboy.system.dal.redis.SystemCacheKey; +import cn.odboy.system.dal.redis.SystemUserOnlineInfoDAO; +import cn.odboy.system.framework.permission.core.CsSecurityHelper; +import cn.odboy.system.framework.permission.core.handler.TokenProvider; +import cn.odboy.system.framework.permission.core.handler.UserDetailsHandler; +import cn.odboy.util.CsRsaEncryptUtil; +import cn.odboy.util.CsStringUtil; +import com.wf.captcha.base.Captcha; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * 授权、根据token获取用户详细信息 + */ +@Slf4j +@RestController +@RequestMapping("/auth") +@Api(tags = "系统:系统授权接口") +public class SystemAuthController { + @Autowired + private CsRedisHelper redisHelper; + @Autowired + private SystemUserOnlineInfoDAO systemUserOnlineInfoDAO; + @Autowired + private TokenProvider tokenProvider; + @Autowired + private AppProperties properties; + @Autowired + private PasswordEncoder passwordEncoder; + @Autowired + private UserDetailsHandler userDetailsService; + + @ApiOperation("登录授权") + @AnonymousPostMapping(value = "/login") + public ResponseEntity> login(@Validated @RequestBody SystemUserLoginArgs loginRequest, HttpServletRequest request) throws Exception { + // 密码解密 + String password = CsRsaEncryptUtil.decryptByPrivateKey(properties.getRsa().getPrivateKey(), loginRequest.getPassword()); + // 查询验证码 + String code = redisHelper.get(loginRequest.getUuid(), String.class); + // 清除验证码 + redisHelper.del(loginRequest.getUuid()); + if (StrUtil.isBlank(code)) { + throw new BadRequestException("验证码不存在或已过期"); + } + if (StrUtil.isBlank(loginRequest.getCode()) || !loginRequest.getCode().equalsIgnoreCase(code)) { + throw new BadRequestException("验证码错误"); + } + // 获取用户信息 + SystemUserJwtVo jwtUser = userDetailsService.loadUserByUsername(loginRequest.getUsername()); + // 验证用户密码 + if (!passwordEncoder.matches(password, jwtUser.getPassword())) { + throw new BadRequestException("登录密码错误"); + } + Authentication authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + // 生成令牌 + String token = tokenProvider.createToken(jwtUser); + // 返回 token 与 用户信息 + Map authInfo = new HashMap<>(2) {{ + put("token", String.format("%s %s", SystemConst.TOKEN_PREFIX, token)); + put("user", BeanUtil.copyProperties(jwtUser, SystemUserInfoVo.class)); + }}; + if (properties.getLogin().isSingle()) { + // 踢掉之前已经登录的token + systemUserOnlineInfoDAO.kickOutByUsername(loginRequest.getUsername()); + } + // 保存在线信息 + systemUserOnlineInfoDAO.saveUserJwtModelByToken(jwtUser, token, request); + // 返回登录信息 + return ResponseEntity.ok(authInfo); + } + + @ApiOperation("获取用户信息") + @PostMapping(value = "/info") + public ResponseEntity getUserInfo() { + SystemUserJwtVo jwtUser = (SystemUserJwtVo) CsSecurityHelper.getCurrentUser(); + SystemUserInfoVo userInfoVo = BeanUtil.copyProperties(jwtUser, SystemUserInfoVo.class); + return ResponseEntity.ok(userInfoVo); + } + + @ApiOperation("获取验证码") + @AnonymousPostMapping(value = "/captcha/getImage") + public ResponseEntity> getCode() { + // 获取运算的结果 + Captcha captcha = properties.getLogin().getCaptchaSetting().getCaptcha(); + String uuid = SystemCacheKey.CAPTCHA_LOGIN + IdUtil.simpleUUID(); + //当验证码类型为 arithmetic时且长度 >= 2 时, captcha.text()的结果有几率为浮点型 + String captchaValue = captcha.text(); + if (captcha.getCharType() - 1 == CaptchaCodeEnum.ARITHMETIC.ordinal() && captchaValue.contains(SystemConst.SYMBOL_DOT)) { + captchaValue = captchaValue.split("\\.")[0]; + } + // 保存 + redisHelper.set(uuid, captchaValue, properties.getLogin().getCaptchaSetting().getExpiration(), TimeUnit.MINUTES); + // 验证码信息 + Map imgResult = new HashMap(2) {{ + put("img", captcha.toBase64()); + put("uuid", uuid); + }}; + return ResponseEntity.ok(imgResult); + } + + @ApiOperation("退出登录") + @AnonymousPostMapping(value = "/logout") + public ResponseEntity logout(HttpServletRequest request) { + String token = tokenProvider.getToken(request); + systemUserOnlineInfoDAO.logoutByToken(token); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemCaptchaController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemCaptchaController.java new file mode 100644 index 00000000..bae95a21 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemCaptchaController.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.odboy.system.constant.SystemCaptchaBizEnum; +import cn.odboy.system.dal.model.SystemCheckEmailCaptchaArgs; +import cn.odboy.system.service.SystemEmailService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/captcha") +@Api(tags = "系统:验证码管理") +public class SystemCaptchaController { + @Autowired + private SystemEmailService systemEmailService; + + @ApiOperation("重置邮箱, 发送验证码") + @PostMapping(value = "/sendResetEmailCaptcha") + public ResponseEntity sendResetEmailCaptcha(@RequestParam String email) { + systemEmailService.sendCaptcha(SystemCaptchaBizEnum.EMAIL_RESET_EMAIL_CODE, email); + return ResponseEntity.ok(null); + } + + @ApiOperation("重置密码,发送验证码") + @PostMapping(value = "/sendResetPasswordCaptcha") + public ResponseEntity sendResetPasswordCaptcha(@RequestParam String email) { + systemEmailService.sendCaptcha(SystemCaptchaBizEnum.EMAIL_RESET_PWD_CODE, email); + return ResponseEntity.ok(null); + } + + @ApiOperation("验证码验证") + @PostMapping(value = "/checkEmailCaptcha") + public ResponseEntity checkEmailCaptcha(@Validated @RequestBody SystemCheckEmailCaptchaArgs args) { + SystemCaptchaBizEnum biEnum = SystemCaptchaBizEnum.getByBizCode(args.getBizCode()); + systemEmailService.checkEmailCaptcha(biEnum, args.getEmail(), args.getCode()); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemComponentController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemComponentController.java new file mode 100644 index 00000000..5fe2c21d --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemComponentController.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.odboy.system.service.SystemDeptService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Api(tags = "系统:组件数据源") +@RequestMapping("/api/component") +public class SystemComponentController { + @Autowired + private SystemDeptService systemDeptService; + + @ApiOperation("查询部门下拉选择数据源") + @PostMapping(value = "/queryDeptSelectDataSource") + @PreAuthorize("@el.check()") + public ResponseEntity queryDeptSelectDataSource() { + return ResponseEntity.ok(systemDeptService.queryDeptSelectDataSource()); + } + + @ApiOperation("查询部门下拉选择Pro数据源") + @PostMapping(value = "/queryDeptSelectProDataSource") + @PreAuthorize("@el.check()") + public ResponseEntity queryDeptSelectProDataSource() { + return ResponseEntity.ok(systemDeptService.queryDeptSelectProDataSource()); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDeptController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDeptController.java new file mode 100644 index 00000000..7fa80d6c --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDeptController.java @@ -0,0 +1,116 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.odboy.base.CsPageArgs; +import cn.odboy.base.CsPageResult; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.model.SystemCreateDeptArgs; +import cn.odboy.system.dal.model.SystemQueryDeptArgs; +import cn.odboy.system.service.SystemDeptService; +import cn.odboy.util.CsPageUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@RestController +@Api(tags = "系统:部门管理") +@RequestMapping("/api/dept") +public class SystemDeptController { + @Autowired + private SystemDeptService systemDeptService; + + @ApiOperation("导出部门数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('dept:list')") + public void exportDept(HttpServletResponse response, SystemQueryDeptArgs criteria) throws Exception { + systemDeptService.exportDeptExcel(systemDeptService.queryAllDept(criteria, false), response); + } + + @ApiOperation("查询部门") + @PostMapping + @PreAuthorize("@el.check('user:list','dept:list')") + public ResponseEntity> queryDept(@Validated @RequestBody CsPageArgs args) throws Exception { + SystemQueryDeptArgs criteria = args.getArgs(); + List depts = systemDeptService.queryAllDept(criteria, true); + return ResponseEntity.ok(CsPageUtil.toPage(depts)); + } + + @ApiOperation("查询部门:根据ID获取同级与上级数据") + @PostMapping("/queryDeptSuperiorTree") + @PreAuthorize("@el.check('user:list','dept:list')") + public ResponseEntity> queryDeptSuperiorTree(@RequestBody List ids, @RequestParam(defaultValue = "false") Boolean exclude) { + Set deptSet = new LinkedHashSet<>(); + for (Long id : ids) { + // 同级数据 + SystemDeptTb dept = systemDeptService.queryDeptById(id); + // 上级数据 + List depts = systemDeptService.querySuperiorDeptListByPid(dept, new ArrayList<>()); + if (exclude) { + for (SystemDeptTb data : depts) { + if (data.getId().equals(dept.getPid())) { + data.setSubCount(data.getSubCount() - 1); + } + } + // 编辑部门时不显示自己以及自己下级的数据, 避免出现PID数据环形问题 + depts = depts.stream().filter(i -> !ids.contains(i.getId())).collect(Collectors.toList()); + } + deptSet.addAll(depts); + } + return ResponseEntity.ok(systemDeptService.buildDeptTree(new ArrayList<>(deptSet))); + } + + @ApiOperation("新增部门") + @PostMapping(value = "/saveDept") + @PreAuthorize("@el.check('dept:add')") + public ResponseEntity saveDept(@Validated @RequestBody SystemCreateDeptArgs args) { + systemDeptService.saveDept(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("修改部门") + @PostMapping(value = "/modifyDept") + @PreAuthorize("@el.check('dept:edit')") + public ResponseEntity modifyDept(@Validated(SystemDeptTb.Update.class) @RequestBody SystemDeptTb args) { + systemDeptService.modifyDept(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("删除部门") + @PostMapping(value = "/removeDeptByIds") + @PreAuthorize("@el.check('dept:del')") + public ResponseEntity removeDeptByIds(@RequestBody Set ids) { + // 获取部门, 和其所有子部门 + Set depts = systemDeptService.traverseDeptByIdWithPids(ids); + // 验证是否被角色或用户关联 + systemDeptService.verifyBindRelationByIds(depts); + systemDeptService.removeDeptByIds(depts); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictController.java new file mode 100644 index 00000000..355f60a3 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictController.java @@ -0,0 +1,98 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.odboy.base.CsPageArgs; +import cn.odboy.base.CsPageResult; +import cn.odboy.system.dal.dataobject.SystemDictTb; +import cn.odboy.system.dal.model.SystemCreateDictArgs; +import cn.odboy.system.dal.model.SystemQueryDictArgs; +import cn.odboy.system.service.SystemDictService; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Set; + +@RestController +@Api(tags = "系统:字典管理") +@RequestMapping("/api/dict") +public class SystemDictController { + @Autowired + private SystemDictService systemDictService; + + @ApiOperation("导出字典数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('dict:list')") + public void exportDict(HttpServletResponse response, SystemQueryDictArgs criteria) throws IOException { + systemDictService.exportDictExcel(systemDictService.queryDictByArgs(criteria), response); + } + + @ApiOperation("查询字典") + @PostMapping(value = "/queryAllDict") + @PreAuthorize("@el.check('dict:list')") + public ResponseEntity> queryAllDict() { + return ResponseEntity.ok(systemDictService.queryDictByArgs(new SystemQueryDictArgs())); + } + + @ApiOperation("查询字典") + @PostMapping + @PreAuthorize("@el.check('dict:list')") + public ResponseEntity> queryDictByArgs(@Validated @RequestBody CsPageArgs args) { + Page page; + SystemQueryDictArgs criteria = args.getArgs(); + if (args.getSize() != null) { + page = new Page<>(args.getPage(), args.getSize()); + } else { + page = new Page<>(criteria.getPage(), criteria.getSize()); + } + return ResponseEntity.ok(systemDictService.queryDictByArgs(criteria, page)); + } + + @ApiOperation("新增字典") + @PostMapping(value = "/saveDict") + @PreAuthorize("@el.check('dict:add')") + public ResponseEntity saveDict(@Validated @RequestBody SystemCreateDictArgs args) { + systemDictService.saveDict(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("修改字典") + @PostMapping(value = "/modifyDictById") + @PreAuthorize("@el.check('dict:edit')") + public ResponseEntity modifyDictById(@Validated(SystemDictTb.Update.class) @RequestBody SystemDictTb args) { + systemDictService.modifyDictById(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("删除字典") + @PostMapping(value = "/removeDictByIds") + @PreAuthorize("@el.check('dict:del')") + public ResponseEntity removeDictByIds(@RequestBody Set ids) { + systemDictService.removeDictByIds(ids); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictDetailController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictDetailController.java new file mode 100644 index 00000000..9e3cd3c3 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictDetailController.java @@ -0,0 +1,93 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.odboy.base.CsPageArgs; +import cn.odboy.base.CsPageResult; +import cn.odboy.system.dal.dataobject.SystemDictDetailTb; +import cn.odboy.system.dal.model.SystemCreateDictDetailArgs; +import cn.odboy.system.dal.model.SystemQueryDictDetailArgs; +import cn.odboy.system.service.SystemDictDetailService; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@Api(tags = "系统:字典详情管理") +@RequestMapping("/api/dictDetail") +public class SystemDictDetailController { + @Autowired + private SystemDictDetailService systemDictDetailService; + + @ApiOperation("查询字典详情") + @PostMapping + public ResponseEntity> queryDictDetailByCrud(@Validated @RequestBody CsPageArgs args) { + return queryDictDetailByArgs(args); + } + + @ApiOperation("查询字典详情") + @PostMapping(value = "/queryDictDetailByArgs") + public ResponseEntity> queryDictDetailByArgs(@Validated @RequestBody CsPageArgs args) { + SystemQueryDictDetailArgs criteria = args.getArgs(); + Page page = new Page<>(args.getPage(), args.getSize()); + return ResponseEntity.ok(systemDictDetailService.queryDictDetailByArgs(criteria, page)); + } + + @ApiOperation("查询多个字典详情") + @GetMapping(value = "/getDictDetailMaps") + public ResponseEntity>> getDictDetailMaps(@RequestParam String dictName) { + String[] names = dictName.split("[,, ]"); + Map> dictMap = new HashMap<>(16); + for (String name : names) { + dictMap.put(name, systemDictDetailService.queryDictDetailByName(name)); + } + return ResponseEntity.ok(dictMap); + } + + @ApiOperation("新增字典详情") + @PostMapping(value = "/saveDictDetail") + @PreAuthorize("@el.check('dict:add')") + public ResponseEntity saveDictDetail(@Validated @RequestBody SystemCreateDictDetailArgs args) { + systemDictDetailService.saveDictDetail(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("修改字典详情") + @PostMapping(value = "/modifyDictDetailById") + @PreAuthorize("@el.check('dict:edit')") + public ResponseEntity modifyDictDetailById(@Validated(SystemDictDetailTb.Update.class) @RequestBody SystemDictDetailTb args) { + systemDictDetailService.modifyDictDetailById(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("删除字典详情") + @PostMapping(value = "/removeDictDetailById") + @PreAuthorize("@el.check('dict:del')") + public ResponseEntity removeDictDetailById(@RequestBody SystemDictDetailTb args) { + systemDictDetailService.removeDictDetailById(args.getId()); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemEmailController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemEmailController.java new file mode 100644 index 00000000..e2a01dc6 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemEmailController.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.odboy.system.dal.dataobject.SystemEmailConfigTb; +import cn.odboy.system.dal.model.SystemSendEmailArgs; +import cn.odboy.system.service.SystemEmailService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 发送邮件 + */ +@RestController +@RequestMapping("api/email") +@Api(tags = "工具:邮件管理") +public class SystemEmailController { + @Autowired + private SystemEmailService systemEmailService; + + @ApiOperation("查询配置") + @PostMapping(value = "/getLastEmailConfig") + public ResponseEntity getLastEmailConfig() { + return ResponseEntity.ok(systemEmailService.getLastEmailConfig()); + } + + @ApiOperation("配置邮件") + @PostMapping(value = "/modifyEmailConfig") + public ResponseEntity modifyEmailConfig(@Validated @RequestBody SystemEmailConfigTb emailConfig) + throws Exception { + systemEmailService.modifyEmailConfig(emailConfig); + return ResponseEntity.ok(null); + } + + @ApiOperation("发送邮件") + @PostMapping(value = "/sendEmail") + public ResponseEntity sendEmail(@Validated @RequestBody SystemSendEmailArgs sendEmailRequest) { + systemEmailService.sendEmail(sendEmailRequest); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemJobController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemJobController.java new file mode 100644 index 00000000..8914e415 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemJobController.java @@ -0,0 +1,94 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.odboy.base.CsPageArgs; +import cn.odboy.base.CsPageResult; +import cn.odboy.system.dal.dataobject.SystemJobTb; +import cn.odboy.system.dal.model.SystemCreateJobArgs; +import cn.odboy.system.dal.model.SystemQueryJobArgs; +import cn.odboy.system.service.SystemJobService; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +@RestController +@Api(tags = "系统:岗位管理") +@RequestMapping("/api/job") +public class SystemJobController { + @Autowired + private SystemJobService systemJobService; + + @ApiOperation("导出岗位数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('job:list')") + public void exportJob(HttpServletResponse response, SystemQueryJobArgs criteria) throws IOException { + systemJobService.exportJobExcel(systemJobService.queryJobByArgs(criteria), response); + } + + @ApiOperation("查询岗位") + @PostMapping(value = "/queryAllEnableJob") + @PreAuthorize("@el.check('job:list','user:list')") + public ResponseEntity> queryJobByArgs(@Validated @RequestBody CsPageArgs args) { + SystemQueryJobArgs criteria = args.getArgs(); + Page page = new Page<>(criteria.getPage(), criteria.getSize()); + return ResponseEntity.ok(systemJobService.queryJobByArgs(criteria, page)); + } + + @ApiOperation("查询岗位") + @PostMapping + @PreAuthorize("@el.check('job:list','user:list')") + public ResponseEntity> queryJobByCrud(@Validated @RequestBody CsPageArgs args) { + return queryJobByArgs(args); + } + + @ApiOperation("新增岗位") + @PostMapping(value = "/saveJob") + @PreAuthorize("@el.check('job:add')") + public ResponseEntity saveJob(@Validated @RequestBody SystemCreateJobArgs args) { + systemJobService.saveJob(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("修改岗位") + @PostMapping(value = "/modifyJobById") + @PreAuthorize("@el.check('job:edit')") + public ResponseEntity modifyJobById(@Validated(SystemJobTb.Update.class) @RequestBody SystemJobTb args) { + systemJobService.modifyJobById(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("删除岗位") + @PostMapping(value = "/removeJobByIds") + @PreAuthorize("@el.check('job:del')") + public ResponseEntity removeJobByIds(@RequestBody Set ids) { + // 验证是否被用户关联 + systemJobService.verifyBindRelationByIds(ids); + systemJobService.removeJobByIds(ids); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemLocalStorageController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemLocalStorageController.java new file mode 100644 index 00000000..f843f9ca --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemLocalStorageController.java @@ -0,0 +1,97 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.odboy.base.CsPageArgs; +import cn.odboy.base.CsPageResult; +import cn.odboy.constant.FileTypeEnum; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.system.dal.dataobject.SystemLocalStorageTb; +import cn.odboy.system.dal.model.SystemQueryStorageArgs; +import cn.odboy.system.service.SystemLocalStorageService; +import cn.odboy.util.CsFileUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@RestController +@Api(tags = "工具:本地存储管理") +@RequestMapping("/api/localStorage") +public class SystemLocalStorageController { + @Autowired + private SystemLocalStorageService localStorageService; + + @ApiOperation("查询文件") + @PostMapping + @PreAuthorize("@el.check('storage:list')") + public ResponseEntity> queryLocalStorage(@Validated @RequestBody CsPageArgs args) { + SystemQueryStorageArgs criteria = args.getArgs(); + Page page = new Page<>(criteria.getPage(), criteria.getSize()); + return ResponseEntity.ok(localStorageService.queryLocalStorage(criteria, page)); + } + + @ApiOperation("导出数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('storage:list')") + public void exportFile(HttpServletResponse response, SystemQueryStorageArgs criteria) throws IOException { + localStorageService.exportLocalStorageExcel(localStorageService.queryLocalStorage(criteria), response); + } + + @ApiOperation("上传文件") + @PostMapping(value = "/uploadFile") + @PreAuthorize("@el.check('storage:add')") + public ResponseEntity uploadFile(@RequestParam String name, @RequestParam("file") MultipartFile file) { + localStorageService.uploadFile(name, file); + return ResponseEntity.ok(null); + } + + @ApiOperation("上传图片") + @PostMapping("/uploadPicture") + public ResponseEntity uploadPicture(@RequestParam MultipartFile file) { + // 判断文件是否为图片 + String suffix = CsFileUtil.getSuffix(file.getOriginalFilename()); + if (!FileTypeEnum.IMAGE.getCode().equals(CsFileUtil.getFileType(suffix))) { + throw new BadRequestException("只能上传图片"); + } + SystemLocalStorageTb localStorage = localStorageService.uploadFile(null, file); + return ResponseEntity.ok(localStorage); + } + + @ApiOperation("修改文件") + @PostMapping(value = "/modifyLocalStorageById") + @PreAuthorize("@el.check('storage:edit')") + public ResponseEntity modifyLocalStorageById(@Validated @RequestBody SystemLocalStorageTb args) { + localStorageService.modifyLocalStorageById(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("多选删除") + @PostMapping(value = "/removeFileByIds") + public ResponseEntity deleteFileByIds(@RequestBody Long[] ids) { + localStorageService.removeFileByIds(ids); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMenuController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMenuController.java new file mode 100644 index 00000000..f80ad000 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMenuController.java @@ -0,0 +1,150 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.hutool.core.collection.CollectionUtil; +import cn.odboy.base.CsPageArgs; +import cn.odboy.base.CsPageResult; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.system.dal.dataobject.SystemMenuTb; +import cn.odboy.system.dal.model.SystemMenuVo; +import cn.odboy.system.dal.model.SystemQueryMenuArgs; +import cn.odboy.system.dal.mysql.SystemMenuMapper; +import cn.odboy.system.framework.permission.core.CsSecurityHelper; +import cn.odboy.system.service.SystemMenuService; +import cn.odboy.util.CsPageUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.*; +import java.util.stream.Collectors; + +@RestController +@Api(tags = "系统:菜单管理") +@RequestMapping("/api/menu") +public class SystemMenuController { + @Autowired + private SystemMenuService systemMenuService; + @Autowired + private SystemMenuMapper systemMenuMapper; + + @ApiOperation("导出菜单数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('menu:list')") + public void exportMenu(HttpServletResponse response, SystemQueryMenuArgs criteria) throws Exception { + systemMenuService.exportMenuExcel(systemMenuService.queryAllMenu(criteria, false), response); + } + + @PostMapping(value = "/buildMenus") + @ApiOperation("获取前端所需菜单") + public ResponseEntity> buildMenus() { + List menuList = systemMenuService.queryMenuListByUserId(CsSecurityHelper.getCurrentUserId()); + List menus = systemMenuService.buildMenuTree(menuList); + return ResponseEntity.ok(systemMenuService.buildMenuVo(menus)); + } + + @ApiOperation("返回全部的菜单") + @PostMapping(value = "/queryMenuListByPid") + @PreAuthorize("@el.check('menu:list','roles:list')") + public ResponseEntity> queryMenuListByPid(@RequestParam Long pid) { + return ResponseEntity.ok(systemMenuService.queryMenuByPid(pid)); + } + + @ApiOperation("根据菜单ID返回所有子节点ID, 包含自身ID") + @PostMapping(value = "/queryChildMenuSet") + @PreAuthorize("@el.check('menu:list','roles:list')") + public ResponseEntity> queryChildMenuSet(@RequestParam Long id) { + Set menuSet = new HashSet<>(); + List menuList = systemMenuService.queryMenuByPid(id); + menuSet.add(systemMenuMapper.selectById(id)); + menuSet = systemMenuService.queryChildMenu(menuList, menuSet); + Set ids = menuSet.stream().map(SystemMenuTb::getId).collect(Collectors.toSet()); + return ResponseEntity.ok(ids); + } + + @PostMapping + @ApiOperation("查询菜单") + @PreAuthorize("@el.check('menu:list')") + public ResponseEntity> queryAllMenuByCrud(@Validated @RequestBody CsPageArgs args) throws Exception { + return queryAllMenu(args); + } + + @PostMapping(value = "/queryMenuByArgs") + @ApiOperation("查询菜单") + @PreAuthorize("@el.check('menu:list')") + public ResponseEntity> queryAllMenu(@Validated @RequestBody CsPageArgs args) throws Exception { + SystemQueryMenuArgs criteria = args.getArgs(); + List menuList = systemMenuService.queryAllMenu(criteria, true); + return ResponseEntity.ok(CsPageUtil.toPage(menuList)); + } + + @ApiOperation("查询菜单:根据ID获取同级与上级数据") + @PostMapping("/queryMenuSuperior") + @PreAuthorize("@el.check('menu:list')") + public ResponseEntity> queryMenuSuperior(@RequestBody List ids) { + Set menus; + if (CollectionUtil.isNotEmpty(ids)) { + menus = new LinkedHashSet<>(systemMenuService.queryMenuByIds(ids)); + for (SystemMenuTb menu : menus) { + List menuList = systemMenuService.querySuperiorMenuList(menu, new ArrayList<>()); + for (SystemMenuTb data : menuList) { + if (data.getId().equals(menu.getPid())) { + data.setSubCount(data.getSubCount() - 1); + } + } + menus.addAll(menuList); + } + // 编辑菜单时不显示自己以及自己下级的数据, 避免出现PID数据环形问题 + menus = menus.stream().filter(i -> !ids.contains(i.getId())).collect(Collectors.toSet()); + return ResponseEntity.ok(systemMenuService.buildMenuTree(new ArrayList<>(menus))); + } + return ResponseEntity.ok(systemMenuService.queryMenuByPid(null)); + } + + @ApiOperation("新增菜单") + @PostMapping(value = "/saveMenu") + @PreAuthorize("@el.check('menu:add')") + public ResponseEntity saveMenu(@Validated @RequestBody SystemMenuTb args) { + if (args.getId() != null) { + throw new BadRequestException("无效参数id"); + } + systemMenuService.saveMenu(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("修改菜单") + @PostMapping(value = "/modifyMenuById") + @PreAuthorize("@el.check('menu:edit')") + public ResponseEntity modifyMenuById(@Validated(SystemMenuTb.Update.class) @RequestBody SystemMenuTb args) { + systemMenuService.modifyMenuById(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("删除菜单") + @PostMapping(value = "/removeMenuByIds") + @PreAuthorize("@el.check('menu:del')") + public ResponseEntity removeMenuByIds(@RequestBody Set ids) { + systemMenuService.removeMenuByIds(ids); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMonitorController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMonitorController.java new file mode 100644 index 00000000..23b1c3a2 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMonitorController.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.odboy.system.service.SystemMonitorService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@Api(tags = "系统-服务监控管理") +@RequestMapping("/api/monitor") +public class SystemMonitorController { + @Autowired + private SystemMonitorService systemMonitorService; + + @PostMapping(value = "/queryServerMonitorInfo") + @ApiOperation("查询服务监控") + @PreAuthorize("@el.check('monitor:list')") + public ResponseEntity> queryServerMonitorInfo() { + return ResponseEntity.ok(systemMonitorService.queryServerMonitorInfo()); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOnlineController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOnlineController.java new file mode 100644 index 00000000..365b5cd9 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOnlineController.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.odboy.base.CsPageArgs; +import cn.odboy.base.CsPageResult; +import cn.odboy.system.dal.model.SystemUserOnlineVo; +import cn.odboy.system.dal.redis.SystemUserOnlineInfoDAO; +import cn.odboy.util.CsDesEncryptUtil; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +@RestController +@RequestMapping("/api/user/online") +@Api(tags = "系统:在线用户管理") +public class SystemOnlineController { + @Autowired + private SystemUserOnlineInfoDAO systemUserOnlineInfoDAO; + + @ApiOperation("查询在线用户") + @PostMapping + @PreAuthorize("@el.check()") + public ResponseEntity> queryOnlineUser(@Validated @RequestBody CsPageArgs args) { + IPage page = new Page<>(args.getPage(), args.getSize()); + return ResponseEntity.ok(systemUserOnlineInfoDAO.queryUserOnlineModelPage(args.getArgs(), page)); + } + + @ApiOperation("导出数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check()") + public void exportOnlineUser(HttpServletResponse response, String username) throws IOException { + systemUserOnlineInfoDAO.downloadUserOnlineModelExcel(systemUserOnlineInfoDAO.queryUserOnlineModelListByUsername(username), response); + } + + @ApiOperation("踢出用户") + @PostMapping(value = "/kickOutUser") + @PreAuthorize("@el.check()") + public ResponseEntity kickOutUser(@RequestBody Set keys) throws Exception { + for (String token : keys) { + // 解密Key + token = CsDesEncryptUtil.desDecrypt(token); + systemUserOnlineInfoDAO.logoutByToken(token); + } + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOssStorageController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOssStorageController.java new file mode 100644 index 00000000..5714d402 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOssStorageController.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.odboy.base.CsPageArgs; +import cn.odboy.base.CsPageResult; +import cn.odboy.system.dal.dataobject.SystemOssStorageTb; +import cn.odboy.system.dal.model.SystemOssStorageVo; +import cn.odboy.system.dal.model.SystemQueryStorageArgs; +import cn.odboy.system.service.SystemOssStorageService; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Slf4j +@RestController +@Api(tags = "工具:OSS存储管理") +@RequestMapping("/api/ossStorage") +public class SystemOssStorageController { + @Autowired + private SystemOssStorageService systemOssStorageService; + + @ApiOperation("查询文件") + @PostMapping + @PreAuthorize("@el.check('storage:list')") + public ResponseEntity> queryOssStorage(@Validated @RequestBody CsPageArgs args) { + SystemQueryStorageArgs criteria = args.getArgs(); + Page page = new Page<>(criteria.getPage(), criteria.getSize()); + return ResponseEntity.ok(systemOssStorageService.queryOssStorage(criteria, page)); + } + + @ApiOperation("导出数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('storage:list')") + public void exportFile(HttpServletResponse response, SystemQueryStorageArgs criteria) throws IOException { + systemOssStorageService.exportOssStorageExcel(systemOssStorageService.queryOssStorage(criteria), response); + } + + @ApiOperation("上传文件") + @PostMapping(value = "/uploadFile") + @PreAuthorize("@el.check('storage:add')") + public ResponseEntity uploadFile(@RequestParam("file") MultipartFile file) { + String fileUrl = systemOssStorageService.uploadFile(file); + return ResponseEntity.ok(fileUrl); + } + + @ApiOperation("多选删除") + @PostMapping(value = "/removeFileByIds") + public ResponseEntity removeFileByIds(@RequestBody Long[] ids) { + systemOssStorageService.removeFileByIds(ids); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemQuartzJobController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemQuartzJobController.java new file mode 100644 index 00000000..5bb328ca --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemQuartzJobController.java @@ -0,0 +1,140 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.odboy.base.CsPageArgs; +import cn.odboy.base.CsPageResult; +import cn.odboy.framework.context.CsSpringBeanHolder; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.system.dal.dataobject.SystemQuartzJobTb; +import cn.odboy.system.dal.dataobject.SystemQuartzLogTb; +import cn.odboy.system.dal.model.SystemQueryQuartzJobArgs; +import cn.odboy.system.dal.model.SystemUpdateQuartzJobArgs; +import cn.odboy.system.service.SystemQuartzJobService; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +@Slf4j +@RestController +@RequestMapping("/api/quartzJob") +@Api(tags = "系统:定时任务管理") +public class SystemQuartzJobController { + @Autowired + private SystemQuartzJobService systemQuartzJobService; + + @ApiOperation("查询定时任务") + @PostMapping + @PreAuthorize("@el.check('quartzJob:list')") + public ResponseEntity> queryQuartzJobByCrud(@Validated @RequestBody CsPageArgs args) { + SystemQueryQuartzJobArgs criteria = args.getArgs(); + Page page = new Page<>(criteria.getPage(), criteria.getSize()); + return ResponseEntity.ok(systemQuartzJobService.queryQuartzJobByArgs(criteria, page)); + } + + @ApiOperation("导出任务数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('quartzJob:list')") + public void exportQuartzJob(HttpServletResponse response, SystemQueryQuartzJobArgs criteria) throws IOException { + systemQuartzJobService.exportQuartzJobExcel(systemQuartzJobService.queryQuartzJobByArgs(criteria), response); + } + + @ApiOperation("导出日志数据") + @GetMapping(value = "/logs/download") + @PreAuthorize("@el.check('quartzJob:list')") + public void exportQuartzJobLog(HttpServletResponse response, SystemQueryQuartzJobArgs criteria) throws IOException { + systemQuartzJobService.exportQuartzLogExcel(systemQuartzJobService.queryQuartzLogByArgs(criteria), response); + } + + @ApiOperation("查询任务执行日志") + @PostMapping(value = "/logs") + @PreAuthorize("@el.check('quartzJob:list')") + public ResponseEntity> queryQuartzJobLog(SystemQueryQuartzJobArgs args) { + Page page = new Page<>(args.getPage(), args.getSize()); + return ResponseEntity.ok(systemQuartzJobService.queryQuartzLogByArgs(args, page)); + } + + @ApiOperation("新增定时任务") + @PostMapping(value = "/createQuartzJob") + @PreAuthorize("@el.check('quartzJob:add')") + public ResponseEntity createQuartzJob(@Validated @RequestBody SystemQuartzJobTb args) { + if (args.getId() != null) { + throw new BadRequestException("无效参数id"); + } + // 验证Bean是不是合法的, 合法的定时任务 Bean 需要用 @Service 定义 + checkBean(args.getBeanName()); + systemQuartzJobService.createJob(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("修改定时任务") + @PutMapping + @PreAuthorize("@el.check('quartzJob:edit')") + public ResponseEntity updateQuartzJob(@Validated(SystemQuartzJobTb.Update.class) @RequestBody SystemUpdateQuartzJobArgs args) { + // 验证Bean是不是合法的, 合法的定时任务 Bean 需要用 @Service 定义 + checkBean(args.getBeanName()); + systemQuartzJobService.modifyQuartzJobResumeCron(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("更改定时任务状态") + @PostMapping(value = "/switchQuartzJobStatus/{id}") + @PreAuthorize("@el.check('quartzJob:edit')") + public ResponseEntity switchQuartzJobStatus(@PathVariable Long id) { + systemQuartzJobService.switchQuartzJobStatus(systemQuartzJobService.getQuartzJobById(id)); + return ResponseEntity.ok(null); + } + + @ApiOperation("执行定时任务") + @PostMapping(value = "/startQuartzJob/{id}") + @PreAuthorize("@el.check('quartzJob:edit')") + public ResponseEntity startQuartzJob(@PathVariable Long id) { + systemQuartzJobService.startQuartzJob(systemQuartzJobService.getQuartzJobById(id)); + return ResponseEntity.ok(null); + } + + @ApiOperation("删除定时任务") + @PostMapping(value = "/removeJobByIds") + @PreAuthorize("@el.check('quartzJob:del')") + public ResponseEntity removeJobByIds(@RequestBody Set ids) { + systemQuartzJobService.removeJobByIds(ids); + return ResponseEntity.ok(null); + } + + /** + * 验证Bean是不是合法的, 合法的定时任务 Bean 需要用 @Service 定义 + * + * @param beanName Bean名称 + */ + private void checkBean(String beanName) { + // 避免调用攻击者可以从SpringContextHolder获得控制jdbcTemplate类 + // 并使用getDeclaredMethod调用jdbcTemplate的queryForMap函数,执行任意sql命令。 + if (!CsSpringBeanHolder.getAllServiceBeanName().contains(beanName)) { + throw new BadRequestException("非法的 Bean,请重新输入!"); + } + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemRoleController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemRoleController.java new file mode 100644 index 00000000..8721bfe4 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemRoleController.java @@ -0,0 +1,148 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.hutool.core.lang.Dict; +import cn.odboy.base.CsPageArgs; +import cn.odboy.base.CsPageResult; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import cn.odboy.system.dal.model.SystemCreateRoleArgs; +import cn.odboy.system.dal.model.SystemQueryRoleArgs; +import cn.odboy.system.framework.permission.core.CsSecurityHelper; +import cn.odboy.system.service.SystemRoleService; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@RestController +@Api(tags = "系统:角色管理") +@RequestMapping("/api/role") +public class SystemRoleController { + @Autowired + private SystemRoleService systemRoleService; + + @ApiOperation("获取单个role") + @PostMapping(value = "/queryRoleById") + @PreAuthorize("@el.check('roles:list')") + public ResponseEntity queryRoleById(@RequestBody SystemRoleTb args) { + return ResponseEntity.ok(systemRoleService.getRoleById(args.getId())); + } + + @ApiOperation("导出角色数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('role:list')") + public void exportRole(HttpServletResponse response, SystemQueryRoleArgs criteria) throws IOException { + systemRoleService.exportRoleExcel(systemRoleService.queryRoleByArgs(criteria), response); + } + + @ApiOperation("返回全部的角色") + @PostMapping(value = "/queryRoleList") + @PreAuthorize("@el.check('roles:list','user:add','user:edit')") + public ResponseEntity> queryRoleList() { + return ResponseEntity.ok(systemRoleService.queryAllRole()); + } + + @ApiOperation("查询角色") + @PostMapping + @PreAuthorize("@el.check('roles:list')") + public ResponseEntity> queryRoleByArgs(@Validated @RequestBody CsPageArgs args) { + SystemQueryRoleArgs criteria = args.getArgs(); + Page page = new Page<>(criteria.getPage(), criteria.getSize()); + return ResponseEntity.ok(systemRoleService.queryRoleByArgs(criteria, page)); + } + + @ApiOperation("获取用户级别") + @PostMapping(value = "/queryRoleLevel") + public ResponseEntity queryRoleLevel() { + return ResponseEntity.ok(Dict.create().set("level", checkRoleLevels(null))); + } + + @ApiOperation("新增角色") + @PostMapping(value = "/saveRole") + @PreAuthorize("@el.check('roles:add')") + public ResponseEntity saveRole(@Validated @RequestBody SystemCreateRoleArgs args) { + checkRoleLevels(args.getLevel()); + systemRoleService.saveRole(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("修改角色") + @PostMapping(value = "/modifyRoleById") + @PreAuthorize("@el.check('roles:edit')") + public ResponseEntity modifyRoleById(@Validated(SystemRoleTb.Update.class) @RequestBody SystemRoleTb args) { + checkRoleLevels(args.getLevel()); + systemRoleService.modifyRoleById(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("修改角色菜单") + @PostMapping(value = "/modifyBindMenuById") + @PreAuthorize("@el.check('roles:edit')") + public ResponseEntity modifyBindMenuById(@RequestBody SystemRoleTb args) { + SystemRoleTb role = systemRoleService.getRoleById(args.getId()); + checkRoleLevels(role.getLevel()); + systemRoleService.modifyBindMenuById(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("删除角色") + @PostMapping(value = "/removeRoleByIds") + @PreAuthorize("@el.check('roles:del')") + public ResponseEntity removeRoleByIds(@RequestBody Set ids) { + for (Long id : ids) { + SystemRoleTb role = systemRoleService.getRoleById(id); + checkRoleLevels(role.getLevel()); + } + // 验证是否被用户关联 + systemRoleService.verifyBindRelationByIds(ids); + systemRoleService.removeRoleByIds(ids); + return ResponseEntity.ok(null); + } + + /** + * 检查用户的角色级别 + * + * @return / + */ + private int checkRoleLevels(Integer level) { + List levels = + systemRoleService.queryRoleByUsersId(CsSecurityHelper.getCurrentUserId()) + .stream() + .map(SystemRoleTb::getLevel) + .collect(Collectors.toList()); + int min = Collections.min(levels); + if (level != null) { + if (level < min) { + throw new BadRequestException("权限不足, 你的角色级别:" + min + ", 低于操作的角色级别:" + level); + } + } + return min; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemUserController.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemUserController.java new file mode 100644 index 00000000..88ca4b8c --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemUserController.java @@ -0,0 +1,237 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.controller; + +import cn.hutool.core.collection.CollectionUtil; +import cn.odboy.base.CsPageArgs; +import cn.odboy.base.CsPageResult; +import cn.odboy.base.CsSelectOptionVo; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.system.constant.SystemCaptchaBizEnum; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import cn.odboy.system.dal.dataobject.SystemUserTb; +import cn.odboy.system.dal.model.SystemQueryUserArgs; +import cn.odboy.system.dal.model.SystemUpdateUserPasswordArgs; +import cn.odboy.system.framework.permission.core.CsSecurityHelper; +import cn.odboy.system.service.*; +import cn.odboy.util.CsPageUtil; +import cn.odboy.util.CsRsaEncryptUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +@Api(tags = "系统:用户管理") +@RestController +@RequestMapping("/api/user") +public class SystemUserController { + @Autowired + private PasswordEncoder passwordEncoder; + @Autowired + private SystemUserService systemUserService; + @Autowired + private SystemDataService systemDataService; + @Autowired + private SystemDeptService systemDeptService; + @Autowired + private SystemRoleService systemRoleService; + @Autowired + private SystemEmailService systemEmailService; + @Autowired + private AppProperties properties; + + @ApiOperation("导出用户数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('user:list')") + public void exportUserExcel(HttpServletResponse response, SystemQueryUserArgs criteria) throws IOException { + systemUserService.exportUserExcel(systemUserService.queryUserByArgs(criteria), response); + } + + @ApiOperation("查询用户") + @PostMapping + @PreAuthorize("@el.check('user:list')") + public ResponseEntity> queryUserByArgs(@Validated @RequestBody CsPageArgs args) { + Page page = new Page<>(args.getPage(), args.getSize()); + SystemQueryUserArgs criteria = args.getArgs(); + if (!ObjectUtils.isEmpty(criteria.getDeptId())) { + criteria.getDeptIds().add(criteria.getDeptId()); + // 先查找是否存在子节点 + List data = systemDeptService.queryDeptByPid(criteria.getDeptId()); + // 然后把子节点的ID都加入到集合中 + criteria.getDeptIds().addAll(systemDeptService.queryChildDeptIdListByDeptIds(data)); + } + // 数据权限 + List dataScopes = systemDataService.queryDeptIdListByArgs(systemUserService.getUserByUsername(CsSecurityHelper.getCurrentUsername())); + // criteria.getDeptIds() 不为空并且数据权限不为空则取交集 + if (!CollectionUtils.isEmpty(criteria.getDeptIds()) && !CollectionUtils.isEmpty(dataScopes)) { + // 取交集 + criteria.getDeptIds().retainAll(dataScopes); + if (!CollectionUtil.isEmpty(criteria.getDeptIds())) { + return ResponseEntity.ok(systemUserService.queryUserByArgs(criteria, page)); + } + } else { + // 否则取并集 + criteria.getDeptIds().addAll(dataScopes); + return ResponseEntity.ok(systemUserService.queryUserByArgs(criteria, page)); + } + return ResponseEntity.ok(CsPageUtil.emptyData()); + } + + @ApiOperation("新增用户") + @PostMapping(value = "/saveUser") + @PreAuthorize("@el.check('user:add')") + public ResponseEntity saveUser(@Validated @RequestBody SystemUserTb args) { + checkLevel(args); + // 默认密码 123456 + args.setPassword(passwordEncoder.encode("123456")); + systemUserService.saveUser(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("修改用户") + @PostMapping(value = "/modifyUserById") + @PreAuthorize("@el.check('user:edit')") + public ResponseEntity modifyUserById(@Validated(SystemUserTb.Update.class) @RequestBody SystemUserTb args) { + checkLevel(args); + systemUserService.modifyUserById(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("修改用户:个人中心") + @PostMapping(value = "modifyUserCenterInfoById") + public ResponseEntity modifyUserCenterInfoById(@Validated(SystemUserTb.Update.class) @RequestBody SystemUserTb args) { + if (!args.getId().equals(CsSecurityHelper.getCurrentUserId())) { + throw new BadRequestException("不能修改他人资料"); + } + systemUserService.modifyUserCenterInfoById(args); + return ResponseEntity.ok(null); + } + + @ApiOperation("删除用户") + @PostMapping(value = "/removeUserByIds") + @PreAuthorize("@el.check('user:del')") + public ResponseEntity removeUserByIds(@RequestBody Set ids) { + for (Long id : ids) { + Integer currentLevel = Collections.min(systemRoleService.queryRoleByUsersId(CsSecurityHelper.getCurrentUserId()).stream().map(SystemRoleTb::getLevel).collect(Collectors.toList())); + Integer optLevel = Collections.min(systemRoleService.queryRoleByUsersId(id).stream().map(SystemRoleTb::getLevel).collect(Collectors.toList())); + if (currentLevel > optLevel) { + throw new BadRequestException("角色权限不足, 不能删除:" + systemUserService.getUserById(id).getUsername()); + } + } + systemUserService.removeUserByIds(ids); + return ResponseEntity.ok(null); + } + + @ApiOperation("修改密码") + @PostMapping(value = "/modifyUserPasswordByUsername") + public ResponseEntity modifyUserPasswordByUsername(@RequestBody SystemUpdateUserPasswordArgs passVo) throws Exception { + String oldPass = CsRsaEncryptUtil.decryptByPrivateKey(properties.getRsa().getPrivateKey(), passVo.getOldPass()); + String newPass = CsRsaEncryptUtil.decryptByPrivateKey(properties.getRsa().getPrivateKey(), passVo.getNewPass()); + SystemUserTb user = systemUserService.getUserByUsername(CsSecurityHelper.getCurrentUsername()); + if (!passwordEncoder.matches(oldPass, user.getPassword())) { + throw new BadRequestException("修改失败,旧密码错误"); + } + if (passwordEncoder.matches(newPass, user.getPassword())) { + throw new BadRequestException("新密码不能与旧密码相同"); + } + systemUserService.modifyUserPasswordByUsername(user.getUsername(), passwordEncoder.encode(newPass)); + return ResponseEntity.ok(null); + } + + @ApiOperation("重置密码") + @PostMapping(value = "/resetUserPasswordByIds") + public ResponseEntity resetUserPasswordByIds(@RequestBody Set ids) { + String defaultPwd = passwordEncoder.encode("123456"); + systemUserService.resetUserPasswordByIds(ids, defaultPwd); + return ResponseEntity.ok(null); + } + + @ApiOperation("修改头像") + @PostMapping(value = "/modifyUserAvatar") + public ResponseEntity modifyUserAvatar(@RequestParam MultipartFile avatar) { + return ResponseEntity.ok(systemUserService.modifyUserAvatar(avatar)); + } + + @ApiOperation("修改邮箱") + @PostMapping(value = "/modifyUserEmailByUsername/{code}") + public ResponseEntity modifyUserEmailByUsername(@PathVariable String code, @RequestBody SystemUserTb args) throws Exception { + String password = CsRsaEncryptUtil.decryptByPrivateKey(properties.getRsa().getPrivateKey(), args.getPassword()); + SystemUserTb user = systemUserService.getUserByUsername(CsSecurityHelper.getCurrentUsername()); + if (!passwordEncoder.matches(password, user.getPassword())) { + throw new BadRequestException("密码错误"); + } + systemEmailService.checkEmailCaptcha(SystemCaptchaBizEnum.EMAIL_RESET_EMAIL_CODE, args.getEmail(), code); + systemUserService.modifyUserEmailByUsername(user.getUsername(), args.getEmail()); + return ResponseEntity.ok(null); + } + + /** + * 如果当前用户的角色级别低于创建用户的角色级别,则抛出权限不足的错误 + * + * @param args / + */ + private void checkLevel(SystemUserTb args) { + Integer currentLevel = Collections.min(systemRoleService.queryRoleByUsersId(CsSecurityHelper.getCurrentUserId()).stream().map(SystemRoleTb::getLevel).collect(Collectors.toList())); + Integer optLevel = systemRoleService.getDeptLevelByRoles(args.getRoles()); + if (currentLevel > optLevel) { + throw new BadRequestException("角色权限不足"); + } + } + + @ApiOperation("查询用户基础数据") + @PostMapping(value = "/queryUserMetadataOptions") + @PreAuthorize("@el.check('user:list')") + public ResponseEntity> queryUserMetadataOptions(@Validated @RequestBody CsPageArgs args) { + int maxPageSize = 50; + SystemQueryUserArgs criteria = args.getArgs(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.and(c -> { + c.eq(SystemUserTb::getPhone, criteria.getBlurry()); + c.or(); + c.eq(SystemUserTb::getEmail, criteria.getBlurry()); + c.or(); + c.like(SystemUserTb::getUsername, criteria.getBlurry()); + c.or(); + c.like(SystemUserTb::getNickName, criteria.getBlurry()); + }); + List collect = systemUserService.queryUserByBlurry(wrapper, new Page<>(criteria.getPage(), maxPageSize)).getRecords().stream().map(m -> { + Map ext = new HashMap<>(1); + ext.put("id", m.getId()); + ext.put("deptId", m.getDeptId()); + ext.put("email", m.getEmail()); + ext.put("phone", m.getPhone()); + return CsSelectOptionVo.builder().label(m.getNickName()).value(String.valueOf(m.getId())).ext(ext).build(); + }).collect(Collectors.toList()); + return ResponseEntity.ok(collect); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDeptTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDeptTb.java new file mode 100644 index 00000000..92091bee --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDeptTb.java @@ -0,0 +1,102 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.dataobject; + +import cn.odboy.base.CsBaseUserTimeTb; +import com.alibaba.fastjson2.annotation.JSONField; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +@Getter +@Setter +@TableName("system_dept") +public class SystemDeptTb extends CsBaseUserTimeTb { + + @NotNull(groups = Update.class) + @TableId(value = "dept_id", type = IdType.AUTO) + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + + @TableField(exist = false) + @JSONField(serialize = false) + @ApiModelProperty(value = "角色") + private Set roles; + + @TableField(exist = false) + @ApiModelProperty(value = "子部门") + private List children; + + @ApiModelProperty(value = "排序") + private Integer deptSort; + + @NotBlank + @ApiModelProperty(value = "部门名称") + private String name; + + @NotNull + @ApiModelProperty(value = "是否启用") + private Boolean enabled; + + @ApiModelProperty(value = "上级部门") + private Long pid; + + @ApiModelProperty(value = "子节点数目", hidden = true) + private Integer subCount = 0; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SystemDeptTb dept = (SystemDeptTb) o; + return Objects.equals(id, dept.id) && Objects.equals(name, dept.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } + + @ApiModelProperty(value = "是否有子节点") + public Boolean getHasChildren() { + return subCount > 0; + } + + @ApiModelProperty(value = "是否为叶子") + public Boolean getLeaf() { + return subCount <= 0; + } + + @ApiModelProperty(value = "标签名称") + public String getLabel() { + return name; + } +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictDetailTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictDetailTb.java new file mode 100644 index 00000000..b25f1269 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictDetailTb.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.dataobject; + +import cn.odboy.base.CsBaseUserTimeTb; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotNull; + +@Getter +@Setter +@TableName("system_dict_detail") +public class SystemDictDetailTb extends CsBaseUserTimeTb { + + @NotNull(groups = Update.class) + @ApiModelProperty(value = "ID", hidden = true) + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @TableField(value = "dict_id") + @ApiModelProperty(hidden = true) + private Long dictId; + + @TableField(exist = false) + @ApiModelProperty(value = "字典") + private SystemDictTb dict; + + @ApiModelProperty(value = "字典标签") + private String label; + + @ApiModelProperty(value = "字典值") + private String value; + + @ApiModelProperty(value = "排序") + private Integer dictSort = 999; +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictTb.java new file mode 100644 index 00000000..7d563e49 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictTb.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.dataobject; + +import cn.odboy.base.CsBaseUserTimeTb; +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Getter +@Setter +@TableName("system_dict") +public class SystemDictTb extends CsBaseUserTimeTb { + + @NotNull(groups = Update.class) + @ApiModelProperty(value = "ID", hidden = true) + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @NotBlank + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "描述") + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String description; +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemEmailConfigTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemEmailConfigTb.java new file mode 100644 index 00000000..bcad5f17 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemEmailConfigTb.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.dataobject; + +import cn.odboy.base.CsObject; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; + +/** + * 邮件配置类,数据存覆盖式存入数据存 + */ +@Data +@TableName("system_email_config") +@EqualsAndHashCode(callSuper = false) +public class SystemEmailConfigTb extends CsObject { + + @TableId("config_id") + private Long id; + + @NotBlank + @ApiModelProperty(value = "邮件服务器SMTP地址") + private String host; + + @NotBlank + @ApiModelProperty(value = "邮件服务器 SMTP 端口") + private String port; + + @NotBlank + @ApiModelProperty(value = "发件者用户名") + private String user; + + @NotBlank + @ApiModelProperty(value = "密码") + private String password; + + @NotBlank + @ApiModelProperty(value = "收件人") + private String fromUser; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemJobTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemJobTb.java new file mode 100644 index 00000000..1e4c0674 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemJobTb.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.dataobject; + +import cn.odboy.base.CsBaseUserTimeTb; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.Objects; + +@Getter +@Setter +@TableName("system_job") +public class SystemJobTb extends CsBaseUserTimeTb { + + @NotNull(groups = Update.class) + @TableId(value = "id", type = IdType.AUTO) + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + + @NotBlank + @ApiModelProperty(value = "岗位名称") + private String name; + + @NotNull + @ApiModelProperty(value = "岗位排序") + private Long jobSort; + + @NotNull + @ApiModelProperty(value = "是否启用") + private Boolean enabled; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SystemJobTb job = (SystemJobTb) o; + return Objects.equals(id, job.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemLocalStorageTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemLocalStorageTb.java new file mode 100644 index 00000000..5c6235d5 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemLocalStorageTb.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.dataobject; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.odboy.base.CsBaseUserTimeTb; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@TableName("system_local_storage") +public class SystemLocalStorageTb extends CsBaseUserTimeTb { + + @TableId(value = "id", type = IdType.AUTO) + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + + @ApiModelProperty(value = "真实文件名") + private String realName; + + @ApiModelProperty(value = "文件名") + private String name; + + @ApiModelProperty(value = "后缀") + private String suffix; + + @ApiModelProperty(value = "路径") + private String path; + + @ApiModelProperty(value = "类型") + private String type; + + @ApiModelProperty(value = "大小") + private String size; + + @ApiModelProperty(value = "日期分组") + private String dateGroup; + + public void copy(SystemLocalStorageTb source) { + BeanUtil.copyProperties(source, this, CopyOptions.create().setIgnoreNullValue(true)); + } +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemMenuTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemMenuTb.java new file mode 100644 index 00000000..b65d8554 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemMenuTb.java @@ -0,0 +1,123 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.dataobject; + +import cn.odboy.base.CsBaseUserTimeTb; +import com.alibaba.fastjson2.annotation.JSONField; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +@Getter +@Setter +@TableName("system_menu") +public class SystemMenuTb extends CsBaseUserTimeTb { + + @NotNull(groups = {Update.class}) + @TableId(value = "menu_id", type = IdType.AUTO) + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + + @TableField(exist = false) + @JSONField(serialize = false) + @ApiModelProperty(value = "菜单角色") + private Set roles; + + @TableField(exist = false) + private List children; + + @ApiModelProperty(value = "菜单标题") + private String title; + + @TableField(value = "name") + @ApiModelProperty(value = "菜单组件名称") + private String componentName; + + @ApiModelProperty(value = "排序") + private Integer menuSort = 999; + + @ApiModelProperty(value = "组件路径") + private String component; + + @ApiModelProperty(value = "路由地址") + private String path; + + @ApiModelProperty(value = "菜单类型,目录、菜单、按钮") + private Integer type; + + @ApiModelProperty(value = "权限标识") + private String permission; + + @ApiModelProperty(value = "菜单图标") + private String icon; + + @ApiModelProperty(value = "缓存") + private Boolean cache; + + @ApiModelProperty(value = "是否隐藏") + private Boolean hidden; + + @ApiModelProperty(value = "上级菜单") + private Long pid; + + @ApiModelProperty(value = "子节点数目", hidden = true) + private Integer subCount = 0; + + @ApiModelProperty(value = "外链菜单") + private Boolean iFrame; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SystemMenuTb menu = (SystemMenuTb) o; + return Objects.equals(id, menu.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @ApiModelProperty(value = "是否有子节点") + public Boolean getHasChildren() { + return subCount > 0; + } + + @ApiModelProperty(value = "是否为叶子") + public Boolean getLeaf() { + return subCount <= 0; + } + + @ApiModelProperty(value = "标签名称") + public String getLabel() { + return title; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOperationLogTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOperationLogTb.java new file mode 100644 index 00000000..521ce63f --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOperationLogTb.java @@ -0,0 +1,101 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.dataobject; + +import cn.odboy.base.CsObject; +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.util.Date; + +/** + *

+ * 系统审计日志 + *

+ * + * @author codegen + * @since 2025-05-12 + */ +@Getter +@Setter +@ToString +@TableName("system_operation_log") +@ApiModel(value = "SystemOperationLog对象", description = "审计日志") +public class SystemOperationLogTb extends CsObject { + + /** + * id + */ + @ApiModelProperty("id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 业务名称 + */ + @TableField("biz_name") + private String bizName; + /** + * 业务方法 + */ + @TableField("method") + private String method; + /** + * 业务参数 + */ + @TableField("params") + private String params; + /** + * 访问来源IP + */ + @TableField("request_ip") + private String requestIp; + /** + * 执行时间 + */ + @TableField("execute_time") + private Long executeTime; + /** + * 用户名 + */ + @TableField("username") + private String username; + /** + * 访问来源地址 + */ + @TableField("address") + private String address; + /** + * 浏览器信息 + */ + @TableField("browser_info") + private String browserInfo; + /** + * 异常详情 + */ + @TableField("exception_detail") + private String exceptionDetail; + /** + * 创建时间 + */ + @TableField(value = "create_time", fill = FieldFill.INSERT) + private Date createTime; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOssStorageTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOssStorageTb.java new file mode 100644 index 00000000..cc27c584 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOssStorageTb.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.dataobject; + +import cn.odboy.base.CsBaseUserTimeTb; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * OSS存储 + *

+ * + * @author codegen + * @since 2025-07-15 + */ +@Getter +@Setter +@TableName("system_oss_storage") +@ApiModel(value = "SystemOssStorage对象", description = "OSS存储") +public class SystemOssStorageTb extends CsBaseUserTimeTb { + @ApiModelProperty("ID") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + @TableField("service_type") + @ApiModelProperty("类型,比如minio") + private String serviceType; + @TableField("endpoint") + @ApiModelProperty("服务地址") + private String endpoint; + @TableField("bucket_name") + @ApiModelProperty("存储桶名称") + private String bucketName; + @TableField("file_name") + @ApiModelProperty("完整文件名称") + private String fileName; + @TableField("file_size") + @ApiModelProperty("文件大小, 单位:字节") + private Long fileSize; + @TableField("file_mime") + @ApiModelProperty("文件类型") + private String fileMime; + @ApiModelProperty("短文件名") + @TableField("file_prefix") + private String filePrefix; + @ApiModelProperty("文件后缀") + @TableField("file_suffix") + private String fileSuffix; + @TableField("file_md5") + @ApiModelProperty("文件md5") + private String fileMd5; + @TableField("file_url") + @ApiModelProperty("文件链接") + private String fileUrl; + @TableField("file_code") + @ApiModelProperty("文件编码") + private String fileCode; + @TableField("object_name") + @ApiModelProperty("对象路径") + private String objectName; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzJobTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzJobTb.java new file mode 100644 index 00000000..7baefc10 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzJobTb.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.dataobject; + +import cn.odboy.base.CsBaseUserTimeTb; +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Getter +@Setter +@TableName("system_quartz_job") +public class SystemQuartzJobTb extends CsBaseUserTimeTb { + + public static final String JOB_KEY = "JOB_KEY"; + + @TableId(value = "id", type = IdType.AUTO) + @NotNull(groups = {Update.class}) + private Long id; + + @TableField(exist = false) + @ApiModelProperty(value = "用于子任务唯一标识", hidden = true) + private String uuid; + + @ApiModelProperty(value = "定时器名称") + private String jobName; + + @NotBlank + @ApiModelProperty(value = "Bean名称") + private String beanName; + + @NotBlank + @ApiModelProperty(value = "方法名称") + private String methodName; + + @ApiModelProperty(value = "参数") + private String params; + + @NotBlank + @ApiModelProperty(value = "cron表达式") + private String cronExpression; + + @ApiModelProperty(value = "状态:暂停或启动") + private Boolean isPause = false; + + @ApiModelProperty(value = "负责人") + private String personInCharge; + + @ApiModelProperty(value = "报警邮箱") + private String email; + + @ApiModelProperty(value = "子任务") + private String subTask; + + @ApiModelProperty(value = "失败后暂停") + private Boolean pauseAfterFailure; + + @NotBlank + @ApiModelProperty(value = "备注") + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String description; +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzLogTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzLogTb.java new file mode 100644 index 00000000..6e896df6 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzLogTb.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.dataobject; + +import cn.odboy.base.CsObject; +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +@Data +@TableName("system_quartz_log") +@EqualsAndHashCode(callSuper = false) +public class SystemQuartzLogTb extends CsObject { + + @TableId(value = "id", type = IdType.AUTO) + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + + @ApiModelProperty(value = "任务名称", hidden = true) + private String jobName; + + @ApiModelProperty(value = "bean名称", hidden = true) + private String beanName; + + @ApiModelProperty(value = "方法名称", hidden = true) + private String methodName; + + @ApiModelProperty(value = "参数", hidden = true) + private String params; + + @ApiModelProperty(value = "cron表达式", hidden = true) + private String cronExpression; + + @ApiModelProperty(value = "状态", hidden = true) + private Boolean isSuccess; + + @ApiModelProperty(value = "异常详情", hidden = true) + private String exceptionDetail; + + @ApiModelProperty(value = "执行耗时", hidden = true) + private Long time; + + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建时间", hidden = true) + private Date createTime; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleMenuTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleMenuTb.java new file mode 100644 index 00000000..25a6287d --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleMenuTb.java @@ -0,0 +1,16 @@ +package cn.odboy.system.dal.dataobject; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +/** + * 角色菜单 + */ +@Getter +@Setter +@TableName("system_roles_menus") +public class SystemRoleMenuTb { + private Long menuId; + private Long roleId; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleTb.java new file mode 100644 index 00000000..3c4af151 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleTb.java @@ -0,0 +1,86 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.dataobject; + +import cn.odboy.base.CsBaseUserTimeTb; +import cn.odboy.system.constant.SystemDataScopeEnum; +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.Objects; +import java.util.Set; + +/** + * 角色 + */ +@Getter +@Setter +@TableName("system_role") +public class SystemRoleTb extends CsBaseUserTimeTb { + + @NotNull(groups = {Update.class}) + @TableId(value = "role_id", type = IdType.AUTO) + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + + @TableField(exist = false) + @ApiModelProperty(value = "用户", hidden = true) + private Set users; + + @TableField(exist = false) + @ApiModelProperty(value = "菜单", hidden = true) + private Set menus; + + @TableField(exist = false) + @ApiModelProperty(value = "部门", hidden = true) + private Set depts; + + @NotBlank + @ApiModelProperty(value = "名称", hidden = true) + private String name; + + @ApiModelProperty(value = "数据权限,全部 、 本级 、 自定义") + private String dataScope = SystemDataScopeEnum.THIS_LEVEL.getValue(); + + @ApiModelProperty(value = "级别,数值越小,级别越大") + private Integer level = 3; + + @ApiModelProperty(value = "描述") + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String description; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SystemRoleTb role = (SystemRoleTb) o; + return Objects.equals(id, role.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserTb.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserTb.java new file mode 100644 index 00000000..159a624a --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserTb.java @@ -0,0 +1,116 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.dataobject; + +import cn.odboy.base.CsBaseUserTimeTb; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.Date; +import java.util.Objects; +import java.util.Set; + +@Getter +@Setter +@TableName("system_user") +public class SystemUserTb extends CsBaseUserTimeTb { + + @NotNull(groups = Update.class) + @TableId(value = "user_id", type = IdType.AUTO) + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + + @TableField(exist = false) + @ApiModelProperty(value = "用户角色") + private Set roles; + + @TableField(exist = false) + @ApiModelProperty(value = "用户岗位") + private Set jobs; + + @TableField(value = "dept_id") + @ApiModelProperty(hidden = true) + private Long deptId; + + @ApiModelProperty(value = "用户部门") + @TableField(exist = false) + private SystemDeptTb dept; + + @NotBlank + @ApiModelProperty(value = "用户名称") + private String username; + + @NotBlank + @ApiModelProperty(value = "用户昵称") + private String nickName; + + @Email + @NotBlank + @ApiModelProperty(value = "邮箱") + private String email; + + @NotBlank + @ApiModelProperty(value = "电话号码") + private String phone; + + @ApiModelProperty(value = "用户性别") + private String gender; + + @ApiModelProperty(value = "头像真实名称", hidden = true) + private String avatarName; + + @ApiModelProperty(value = "头像存储的路径", hidden = true) + private String avatarPath; + + @ApiModelProperty(value = "密码") + private String password; + + @NotNull + @ApiModelProperty(value = "是否启用") + private Boolean enabled; + + @ApiModelProperty(value = "是否为admin账号", hidden = true) + private Boolean isAdmin = false; + + @ApiModelProperty(value = "最后修改密码的时间", hidden = true) + private Date pwdResetTime; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SystemUserTb user = (SystemUserTb) o; + return Objects.equals(id, user.id) && Objects.equals(username, user.username); + } + + @Override + public int hashCode() { + return Objects.hash(id, username); + } +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCheckEmailCaptchaArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCheckEmailCaptchaArgs.java new file mode 100644 index 00000000..66192de3 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCheckEmailCaptchaArgs.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; + +@Getter +@Setter +public class SystemCheckEmailCaptchaArgs extends CsObject { + @NotBlank(message = "邮箱不能为空") + private String email; + @NotBlank(message = "验证码不能为空") + private String code; + @NotBlank(message = "验证码业务标识不能为空") + private String bizCode; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateDeptArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateDeptArgs.java new file mode 100644 index 00000000..447f2c68 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateDeptArgs.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemCreateDeptArgs extends CsObject { + @NotBlank(message = "部门名称必填") + private String name; + @NotNull(message = "部门排序必填") + private Integer deptSort; + private Boolean enabled; + private String isTop; + private Long pid; + private Integer subCount = 0; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateDictArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateDictArgs.java new file mode 100644 index 00000000..591bfeda --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateDictArgs.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemCreateDictArgs extends CsObject { + @NotBlank(message = "字典名称必填") + private String name; + private String description; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateDictDetailArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateDictDetailArgs.java new file mode 100644 index 00000000..55055fa9 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateDictDetailArgs.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import cn.odboy.system.dal.dataobject.SystemDictTb; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemCreateDictDetailArgs extends CsObject { + @NotNull(message = "参数dict必填") + private SystemDictTb dict; + @NotBlank(message = "字典标签必填") + private String label; + @NotBlank(message = "字典值必填") + private String value; + @NotNull(message = "字典排序必填") + private Integer dictSort; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateJobArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateJobArgs.java new file mode 100644 index 00000000..6cf549d5 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateJobArgs.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemCreateJobArgs extends CsObject { + @NotBlank(message = "职位名称必填") + private String name; + @NotNull(message = "职位排序必填") + private Integer jobSort; + private Boolean enabled; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateRoleArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateRoleArgs.java new file mode 100644 index 00000000..a4ef2d16 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemCreateRoleArgs.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.Set; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemCreateRoleArgs extends CsObject { + @NotBlank(message = "角色名称必填") + private String name; + @NotNull(message = "角色级别必填") + private Integer level; + @NotBlank(message = "数据范围必填") + private String dataScope; + private String description; + /** + * 关联的部门 + */ + private Long id; + private Set depts; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemMenuMetaVo.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemMenuMetaVo.java new file mode 100644 index 00000000..3dc27e10 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemMenuMetaVo.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class SystemMenuMetaVo extends CsObject { + + @ApiModelProperty(value = "菜单标题") + private String title; + + @ApiModelProperty(value = "菜单图标") + private String icon; + + @ApiModelProperty(value = "缓存") + private Boolean noCache; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemMenuVo.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemMenuVo.java new file mode 100644 index 00000000..606cb891 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemMenuVo.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 构建前端路由时用到 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemMenuVo extends CsObject { + + @ApiModelProperty(value = "菜单名称") + private String name; + + @ApiModelProperty(value = "路径") + private String path; + + @ApiModelProperty(value = "隐藏状态") + private Boolean hidden; + + @ApiModelProperty(value = "重定向") + private String redirect; + + @ApiModelProperty(value = "组件") + private String component; + + @ApiModelProperty(value = "总是显示") + private Boolean alwaysShow; + + @ApiModelProperty(value = "元数据") + private SystemMenuMetaVo meta; + + @ApiModelProperty(value = "子路由") + private List children; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemOssStorageVo.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemOssStorageVo.java new file mode 100644 index 00000000..80a8aa38 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemOssStorageVo.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.system.dal.dataobject.SystemOssStorageTb; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SystemOssStorageVo extends SystemOssStorageTb { + private String fileSizeDesc; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemProductLineTreeVo.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemProductLineTreeVo.java new file mode 100644 index 00000000..1d45162a --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemProductLineTreeVo.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class SystemProductLineTreeVo extends CsObject { + private String value; + private String label; + private List children; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemProductLineVo.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemProductLineVo.java new file mode 100644 index 00000000..bd1ab894 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemProductLineVo.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SystemProductLineVo extends CsObject { + /** + * 部门ID(select的value) + */ + private Long value; + /** + * 产品线,如"研发部/华南分部/devops组" + */ + private String label; + /** + * ID路径,如"1-2-3" + */ + private String idPath; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryDeptArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryDeptArgs.java new file mode 100644 index 00000000..a18a9c97 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryDeptArgs.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +public class SystemQueryDeptArgs { + + @ApiModelProperty(value = "部门id集合") + private List ids; + + @ApiModelProperty(value = "部门名称") + private String name; + + @ApiModelProperty(value = "是否启用") + private Boolean enabled; + + @ApiModelProperty(value = "上级部门") + private Long pid; + + @ApiModelProperty(value = "PID为空查询") + private Boolean pidIsNull; + + @ApiModelProperty(value = "创建时间") + private List createTime; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryDictArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryDictArgs.java new file mode 100644 index 00000000..9cf4af25 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryDictArgs.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class SystemQueryDictArgs { + + @ApiModelProperty(value = "模糊查询") + private String blurry; + + @ApiModelProperty(value = "页码", example = "1") + private Integer page = 1; + + @ApiModelProperty(value = "每页数据量", example = "10") + private Integer size = 10; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryDictDetailArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryDictDetailArgs.java new file mode 100644 index 00000000..9e3aa391 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryDictDetailArgs.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SystemQueryDictDetailArgs extends CsObject { + @ApiModelProperty(value = "标签") + private String label; + + @ApiModelProperty(value = "字典名称") + private String dictName; +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryJobArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryJobArgs.java new file mode 100644 index 00000000..d17dc2ef --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryJobArgs.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; +import java.util.List; + +@Data +@NoArgsConstructor +public class SystemQueryJobArgs { + + @ApiModelProperty(value = "岗位名称") + private String name; + + @ApiModelProperty(value = "是否启用") + private Boolean enabled; + + @ApiModelProperty(value = "创建时间") + private List createTime; + + @ApiModelProperty(value = "页码", example = "1") + private Integer page = 1; + + @ApiModelProperty(value = "每页数据量", example = "10") + private Integer size = 10; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryMenuArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryMenuArgs.java new file mode 100644 index 00000000..2858bbd8 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryMenuArgs.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +public class SystemQueryMenuArgs { + + @ApiModelProperty(value = "模糊查询") + private String blurry; + + @ApiModelProperty(value = "创建时间") + private List createTime; + + @ApiModelProperty(value = "PID为空查询") + private Boolean pidIsNull; + + @ApiModelProperty(value = "PID") + private Long pid; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryQuartzJobArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryQuartzJobArgs.java new file mode 100644 index 00000000..2d25b035 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryQuartzJobArgs.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +public class SystemQueryQuartzJobArgs { + + @ApiModelProperty(value = "定时任务名称") + private String jobName; + + @ApiModelProperty(value = "是否成功") + private Boolean isSuccess; + + @ApiModelProperty(value = "创建时间") + private List createTime; + + @ApiModelProperty(value = "页码", example = "1") + private Integer page = 1; + + @ApiModelProperty(value = "每页数据量", example = "10") + private Integer size = 10; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryRoleArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryRoleArgs.java new file mode 100644 index 00000000..caea55f7 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryRoleArgs.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +public class SystemQueryRoleArgs { + + @ApiModelProperty(value = "模糊查询") + private String blurry; + + @ApiModelProperty(value = "创建时间") + private List createTime; + + @ApiModelProperty(value = "页码", example = "1") + private Integer page = 1; + + @ApiModelProperty(value = "每页数据量", example = "10") + private Integer size = 10; + + @ApiModelProperty(value = "偏移量", hidden = true) + private long offset; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryStorageArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryStorageArgs.java new file mode 100644 index 00000000..1cee5eab --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryStorageArgs.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +public class SystemQueryStorageArgs { + + @ApiModelProperty(value = "模糊查询") + private String blurry; + + @ApiModelProperty(value = "创建时间") + private List createTime; + + @ApiModelProperty(value = "页码", example = "1") + private Integer page = 1; + + @ApiModelProperty(value = "每页数据量", example = "10") + private Integer size = 10; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryUserArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryUserArgs.java new file mode 100644 index 00000000..dcb76e1a --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemQueryUserArgs.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemQueryUserArgs extends CsObject { + + @ApiModelProperty(value = "ID") + private Long id; + + @ApiModelProperty(value = "多个ID") + private Set deptIds = new HashSet<>(); + + @ApiModelProperty(value = "模糊查询") + private String blurry; + + @ApiModelProperty(value = "是否启用") + private Boolean enabled; + + @ApiModelProperty(value = "部门ID") + private Long deptId; + + @ApiModelProperty(value = "创建时间") + private List createTime; + + @ApiModelProperty(value = "页码", example = "1") + private Integer page = 1; + + @ApiModelProperty(value = "每页数据量", example = "10") + private Integer size = 10; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemRoleCodeVo.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemRoleCodeVo.java new file mode 100644 index 00000000..2475008d --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemRoleCodeVo.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; + +/** + * 避免序列化问题 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SystemRoleCodeVo implements GrantedAuthority { + + @ApiModelProperty(value = "角色名") + private String authority; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemSendEmailArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemSendEmailArgs.java new file mode 100644 index 00000000..7d5166ac --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemSendEmailArgs.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import java.util.List; + +/** + * 发送邮件时,接收参数的类 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SystemSendEmailArgs { + + @NotEmpty + @ApiModelProperty(value = "收件人") + private List tos; + + @NotBlank + @ApiModelProperty(value = "主题") + private String subject; + + @NotBlank + @ApiModelProperty(value = "内容") + private String content; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemSimpleUserVo.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemSimpleUserVo.java new file mode 100644 index 00000000..19d76803 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemSimpleUserVo.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.dataobject.SystemJobTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Set; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemSimpleUserVo extends CsObject { + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + @ApiModelProperty(value = "用户角色") + private Set roles; + @ApiModelProperty(value = "用户岗位") + private Set jobs; + private Long deptId; + @TableField(exist = false) + private SystemDeptTb dept; + @ApiModelProperty(value = "用户名称") + private String username; + @ApiModelProperty(value = "用户昵称") + private String nickName; + @ApiModelProperty(value = "邮箱") + private String email; + @ApiModelProperty(value = "电话号码") + private String phone; + @ApiModelProperty(value = "用户性别") + private String gender; + @ApiModelProperty(value = "头像真实名称", hidden = true) + private String avatarName; + @ApiModelProperty(value = "是否启用") + private Boolean enabled; + @ApiModelProperty(value = "是否为admin账号", hidden = true) + private Boolean isAdmin = false; +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUpdateQuartzJobArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUpdateQuartzJobArgs.java new file mode 100644 index 00000000..d226c074 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUpdateQuartzJobArgs.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemUpdateQuartzJobArgs extends CsObject { + @NotNull + private Long id; + private String uuid; + private String jobName; + @NotBlank + private String beanName; + @NotBlank + private String methodName; + private String params; + @NotBlank + private String cronExpression; + private Boolean isPause = false; + private String personInCharge; + private String email; + private String subTask; + private Boolean pauseAfterFailure; + @NotBlank + private String description; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUpdateUserPasswordArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUpdateUserPasswordArgs.java new file mode 100644 index 00000000..3a235a25 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUpdateUserPasswordArgs.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 修改密码 + */ +@Data +public class SystemUpdateUserPasswordArgs { + + @ApiModelProperty(value = "旧密码") + private String oldPass; + + @ApiModelProperty(value = "新密码") + private String newPass; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserInfoVo.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserInfoVo.java new file mode 100644 index 00000000..21f16de8 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserInfoVo.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.base.CsObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemUserInfoVo extends CsObject { + private SystemSimpleUserVo user; + private List dataScopes; + private List authorities; + + public Set getRoles() { + return authorities.stream().map(SystemRoleCodeVo::getAuthority).collect(Collectors.toSet()); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserJwtVo.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserJwtVo.java new file mode 100644 index 00000000..48c09af6 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserJwtVo.java @@ -0,0 +1,82 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import cn.odboy.system.dal.dataobject.SystemUserTb; +import com.alibaba.fastjson2.annotation.JSONField; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Getter +@AllArgsConstructor +public class SystemUserJwtVo implements UserDetails { + + @ApiModelProperty(value = "用户") + private final SystemUserTb user; + + @ApiModelProperty(value = "数据权限") + private final List dataScopes; + + @ApiModelProperty(value = "角色") + private final List authorities; + + public Set getRoles() { + return authorities.stream().map(SystemRoleCodeVo::getAuthority).collect(Collectors.toSet()); + } + + @Override + @JSONField(serialize = false) + public String getPassword() { + return user.getPassword(); + } + + @Override + @JSONField(serialize = false) + public String getUsername() { + return user.getUsername(); + } + + @Override + @JSONField(serialize = false) + public boolean isAccountNonExpired() { + return true; + } + + @Override + @JSONField(serialize = false) + public boolean isAccountNonLocked() { + return true; + } + + @Override + @JSONField(serialize = false) + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + @JSONField(serialize = false) + public boolean isEnabled() { + return user.getEnabled(); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserLoginArgs.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserLoginArgs.java new file mode 100644 index 00000000..82f2d1ca --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserLoginArgs.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; + +@Getter +@Setter +public class SystemUserLoginArgs { + + @NotBlank + @ApiModelProperty(value = "用户名") + private String username; + + @NotBlank + @ApiModelProperty(value = "密码") + private String password; + + @ApiModelProperty(value = "验证码") + private String code; + + @ApiModelProperty(value = "验证码的key") + private String uuid = ""; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserOnlineVo.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserOnlineVo.java new file mode 100644 index 00000000..6ca48d2c --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/SystemUserOnlineVo.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +/** + * 在线用户 + */ +@Data +public class SystemUserOnlineVo { + @ApiModelProperty(value = "Token编号") + private String uid; + + @ApiModelProperty(value = "用户名") + private String userName; + + @ApiModelProperty(value = "昵称") + private String nickName; + + @ApiModelProperty(value = "岗位") + private String dept; + + @ApiModelProperty(value = "浏览器") + private String browser; + + @ApiModelProperty(value = "IP") + private String ip; + + @ApiModelProperty(value = "地址") + private String address; + + @ApiModelProperty(value = "token") + private String key; + + @ApiModelProperty(value = "登录时间") + private Date loginTime; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDeptMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDeptMapper.java new file mode 100644 index 00000000..2054e235 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDeptMapper.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.model.SystemQueryDeptArgs; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 部门 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemDeptMapper extends BaseMapper { + List selectDeptByArgs(@Param("criteria") SystemQueryDeptArgs criteria); + + List selectDeptByPid(@Param("pid") Long pid); + + int countDeptByPid(@Param("pid") Long pid); + + List selectDeptByPidIsNull(); + + List selectDeptByRoleId(@Param("roleId") Long roleId); + + void updateDeptSubCountById(@Param("count") Integer count, @Param("id") Long id); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictDetailMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictDetailMapper.java new file mode 100644 index 00000000..0afa8647 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictDetailMapper.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemDictDetailTb; +import cn.odboy.system.dal.model.SystemQueryDictDetailArgs; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Set; + +/** + * 字典明细 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemDictDetailMapper extends BaseMapper { + IPage selectDictDetailByArgs(@Param("criteria") SystemQueryDictDetailArgs criteria, Page page); + + List selectDictDetailByArgs(@Param("criteria") SystemQueryDictDetailArgs criteria); + + void deleteDictDetailByDictIds(@Param("dictIds") Set dictIds); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictMapper.java new file mode 100644 index 00000000..8fa684db --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictMapper.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemDictTb; +import cn.odboy.system.dal.model.SystemQueryDictArgs; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 字典 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemDictMapper extends BaseMapper { + IPage selectDictByArgs(@Param("criteria") SystemQueryDictArgs criteria, Page page); + + List selectDictByArgs(@Param("criteria") SystemQueryDictArgs criteria); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemEmailConfigMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemEmailConfigMapper.java new file mode 100644 index 00000000..c85d2568 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemEmailConfigMapper.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemEmailConfigTb; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 邮件配置 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemEmailConfigMapper extends BaseMapper { +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemJobMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemJobMapper.java new file mode 100644 index 00000000..5eab31ee --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemJobMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemJobTb; +import cn.odboy.system.dal.model.SystemQueryJobArgs; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 职位 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemJobMapper extends BaseMapper { + SystemJobTb getJobByName(@Param("name") String name); + + IPage selectJobByArgs(@Param("criteria") SystemQueryJobArgs criteria, Page page); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemLocalStorageMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemLocalStorageMapper.java new file mode 100644 index 00000000..0ac47583 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemLocalStorageMapper.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemLocalStorageTb; +import cn.odboy.system.dal.model.SystemQueryStorageArgs; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 本地存储记录 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemLocalStorageMapper extends BaseMapper { + IPage selectLocalStorageByArgs(@Param("criteria") SystemQueryStorageArgs criteria, Page page); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemMenuMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemMenuMapper.java new file mode 100644 index 00000000..ddaf540e --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemMenuMapper.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemMenuTb; +import cn.odboy.system.dal.model.SystemQueryMenuArgs; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * 菜单 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemMenuMapper extends BaseMapper { + + List selectMenuByArgs(@Param("criteria") SystemQueryMenuArgs criteria); + + LinkedHashSet selectMenuByRoleIdsAndType(@Param("roleIds") Set roleIds, @Param("type") Integer type); + + List selectMenuByPidIsNullOrderByMenuSort(); + + List selectMenuByPidOrderByMenuSort(@Param("pid") Long pid); + + SystemMenuTb getMenuByTitle(@Param("title") String title); + + SystemMenuTb getMenuByComponentName(@Param("name") String name); + + Integer countMenuByPid(@Param("pid") Long pid); + + void updateMenuSubCntByMenuId(@Param("count") int count, @Param("menuId") Long menuId); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOperationLogMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOperationLogMapper.java new file mode 100644 index 00000000..7b939e58 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOperationLogMapper.java @@ -0,0 +1,25 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemOperationLogTb; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SystemOperationLogMapper extends BaseMapper { +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOssStorageMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOssStorageMapper.java new file mode 100644 index 00000000..538a8e43 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOssStorageMapper.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemOssStorageTb; +import cn.odboy.system.dal.model.SystemQueryStorageArgs; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + *

+ * OSS存储 Mapper 接口 + *

+ * + * @author codegen + * @since 2025-07-15 + */ +@Mapper +public interface SystemOssStorageMapper extends BaseMapper { + IPage selectOssStorageByArgs(SystemQueryStorageArgs criteria, Page page); + + List selectOssStorageByArgs(SystemQueryStorageArgs criteria); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzJobMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzJobMapper.java new file mode 100644 index 00000000..6cae6098 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzJobMapper.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemQuartzJobTb; +import cn.odboy.system.dal.model.SystemQueryQuartzJobArgs; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 定时任务 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemQuartzJobMapper extends BaseMapper { + IPage selectQuartzJobByArgs(@Param("criteria") SystemQueryQuartzJobArgs criteria, Page page); + + List selectAllEnableQuartzJob(); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzLogMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzLogMapper.java new file mode 100644 index 00000000..1ac47634 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzLogMapper.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemQuartzLogTb; +import cn.odboy.system.dal.model.SystemQueryQuartzJobArgs; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 定时任务日志 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemQuartzLogMapper extends BaseMapper { + IPage selectQuartzLogByArgs(@Param("criteria") SystemQueryQuartzJobArgs criteria, Page page); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleDeptMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleDeptMapper.java new file mode 100644 index 00000000..672b384e --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleDeptMapper.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Set; + +/** + * 角色部门关联 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemRoleDeptMapper { + void batchInsertRoleDept(@Param("depts") Set depts, @Param("roleId") Long roleId); + + void batchDeleteRoleDept(@Param("roleIds") Set roleIds); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMapper.java new file mode 100644 index 00000000..9085bcf6 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMapper.java @@ -0,0 +1,50 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import cn.odboy.system.dal.model.SystemQueryRoleArgs; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Set; + +/** + * 角色 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemRoleMapper extends BaseMapper { + List selectAllRole(); + + List selectRoleByArgs(@Param("criteria") SystemQueryRoleArgs criteria); + + Long countRoleByArgs(@Param("criteria") SystemQueryRoleArgs criteria); + + SystemRoleTb getRoleByName(@Param("name") String name); + + SystemRoleTb getRoleById(@Param("roleId") Long roleId); + + List selectRoleByUserId(@Param("userId") Long userId); + + Integer countRoleByDeptIds(@Param("deptIds") Set deptIds); + + List selectRoleByMenuId(@Param("menuId") Long menuId); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMenuMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMenuMapper.java new file mode 100644 index 00000000..5910b20f --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMenuMapper.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemMenuTb; +import cn.odboy.system.dal.dataobject.SystemRoleMenuTb; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Set; + +/** + * 角色菜单关联 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemRoleMenuMapper extends BaseMapper { + void batchInsertRoleMenu(@Param("menus") Set menus, @Param("roleId") Long roleId); + + void batchDeleteRoleMenu(@Param("roleIds") Set roleIds); + + void deleteRoleMenuByRoleId(@Param("roleId") Long roleId); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserJobMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserJobMapper.java new file mode 100644 index 00000000..f8e4f517 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserJobMapper.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemJobTb; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Set; + +/** + * 用户职位关联 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemUserJobMapper { + void batchInsertUserJob(@Param("jobs") Set jobs, @Param("userId") Long userId); + + void batchDeleteUserJob(@Param("userIds") Set userIds); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserMapper.java new file mode 100644 index 00000000..deb6e8aa --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserMapper.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemUserTb; +import cn.odboy.system.dal.model.SystemQueryUserArgs; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Set; + +@Mapper +public interface SystemUserMapper extends BaseMapper { + List selectUserByArgs(@Param("criteria") SystemQueryUserArgs criteria); + + IPage selectUserByArgs(@Param("criteria") SystemQueryUserArgs criteria, Page page); + + SystemUserTb getUserByUsername(@Param("username") String username); + + SystemUserTb getUserByEmail(@Param("email") String email); + + SystemUserTb getUserByPhone(@Param("phone") String phone); + + List selectUserByRoleId(@Param("roleId") Long roleId); + + List selectUserByDeptId(@Param("deptId") Long deptId); + + List selectUserByMenuId(@Param("menuId") Long menuId); + + Long countUserByJobIds(@Param("jobIds") Set jobIds); + + Long countUserByDeptIds(@Param("deptIds") Set deptIds); + + Long countUserByRoleIds(@Param("roleIds") Set roleIds); + + void updateUserPasswordByUsername(@Param("username") String username, @Param("password") String password); + + void updateUserEmailByUsername(@Param("username") String username, @Param("email") String email); + + void batchUpdatePassword(@Param("password") String password, @Param("userIds") Set userIds); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserRoleMapper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserRoleMapper.java new file mode 100644 index 00000000..33d83204 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserRoleMapper.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.mysql; + +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Set; + +@Mapper +public interface SystemUserRoleMapper { + void batchInsertUserRole(@Param("roles") Set roles, @Param("userId") Long userId); + + void batchDeleteUserRole(@Param("userIds") Set userIds); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemCacheKey.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemCacheKey.java new file mode 100644 index 00000000..5c3929d2 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemCacheKey.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.dal.redis; + +/** + * 关于缓存的Key集合 + */ +public interface SystemCacheKey { + + /** + * 用户 + */ + String USER_ID = "user::id:"; + + /** + * 数据 + */ + String DATA_USER = "data::user:"; + + /** + * 菜单 + */ + String MENU_ID = "menu::id:"; + String MENU_USER = "menu::user:"; + + /** + * 角色授权 + */ + String ROLE_AUTH = "role::auth:"; + String ROLE_USER = "role::user:"; + + /** + * 角色信息 + */ + String ROLE_ID = "role::id:"; + + /** + * 部门 + */ + String DEPT_ID = "dept::id:"; + + /** + * 岗位 + */ + String JOB_ID = "job::id:"; + + /** + * 数据字典 + */ + String DICT_NAME = "dict::name:"; + + /** + * 在线用户 + */ + String ONLINE_USER = "online::token:"; + + /** + * 登录验证码 + */ + String CAPTCHA_LOGIN = "captcha::login:"; + + /** + * 用户信息 + */ + String USER_INFO = "user::info:"; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemRedisKey.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemRedisKey.java new file mode 100644 index 00000000..f7c63ae8 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemRedisKey.java @@ -0,0 +1,64 @@ +package cn.odboy.system.dal.redis; + +/** + * 关于缓存的Key集合 + */ +public interface SystemRedisKey { + + /** + * 用户 + */ + String USER_ID = "user::id:"; + + /** + * 数据 + */ + String DATA_USER = "data::user:"; + + /** + * 菜单 + */ + String MENU_ID = "menu::id:"; + String MENU_USER = "menu::user:"; + + /** + * 角色授权 + */ + String ROLE_AUTH = "role::auth:"; + String ROLE_USER = "role::user:"; + + /** + * 角色信息 + */ + String ROLE_ID = "role::id:"; + + /** + * 部门 + */ + String DEPT_ID = "dept::id:"; + + /** + * 岗位 + */ + String JOB_ID = "job::id:"; + + /** + * 数据字典 + */ + String DICT_NAME = "dict::name:"; + + /** + * 在线用户 + */ + String ONLINE_USER = "online::token:"; + + /** + * 登录验证码 + */ + String CAPTCHA_LOGIN = "captcha::login:"; + + /** + * 用户信息 + */ + String USER_INFO = "user::info:"; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserInfoDAO.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserInfoDAO.java new file mode 100644 index 00000000..ff201391 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserInfoDAO.java @@ -0,0 +1,61 @@ +package cn.odboy.system.dal.redis; + +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.framework.redis.CsRedisHelper; +import cn.odboy.system.dal.model.SystemUserJwtVo; +import cn.odboy.util.CsStringUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * 用户缓存管理 + */ +@Component +public class SystemUserInfoDAO { + @Autowired + private CsRedisHelper redisHelper; + + /** + * 添加缓存到Redis + * + * @param userName 用户名 + */ + @Async + public void saveUserLoginInfoByUserName(String userName, SystemUserJwtVo user) { + if (StrUtil.isNotEmpty(userName)) { + // 添加数据, 避免数据同时过期(2小时左右) + long time = 7200 + RandomUtil.randomInt(900, 1800); + redisHelper.set(SystemRedisKey.USER_INFO + userName, user, time); + } + } + + /** + * 清理用户缓存信息 用户信息变更时 + * + * @param userName 用户名 + */ + @Async + public void deleteUserLoginInfoByUserName(String userName) { + if (StrUtil.isNotEmpty(userName)) { + // 清除数据 + redisHelper.del(SystemRedisKey.USER_INFO + userName); + } + } + + /** + * 返回用户缓存 + * + * @param username 用户名 + * @return UserJwtVo + */ + + public SystemUserJwtVo getUserLoginInfoByUserName(String username) { + if (StrUtil.isNotEmpty(username)) { + // 获取数据 + return redisHelper.get(SystemRedisKey.USER_INFO + username, SystemUserJwtVo.class); + } + return null; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserOnlineInfoDAO.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserOnlineInfoDAO.java new file mode 100644 index 00000000..2bf743ac --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserOnlineInfoDAO.java @@ -0,0 +1,155 @@ +package cn.odboy.system.dal.redis; + +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.CsPageResult; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.redis.CsRedisHelper; +import cn.odboy.system.dal.model.SystemUserJwtVo; +import cn.odboy.system.dal.model.SystemUserOnlineVo; +import cn.odboy.system.framework.permission.core.handler.TokenProvider; +import cn.odboy.util.*; +import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +@AllArgsConstructor +public class SystemUserOnlineInfoDAO { + private final AppProperties properties; + private final TokenProvider tokenProvider; + private final CsRedisHelper redisHelper; + + /** + * 保存在线用户信息 + * + * @param userJwtVo / + * @param token / + * @param request / + */ + + public void saveUserJwtModelByToken(SystemUserJwtVo userJwtVo, String token, HttpServletRequest request) { + String dept = userJwtVo.getUser().getDept().getName(); + String ip = CsBrowserUtil.getIp(request); + String id = tokenProvider.getId(token); + String version = CsBrowserUtil.getVersion(request); + String address = CsIPUtil.getCityInfo(ip); + SystemUserOnlineVo userOnlineVo = null; + try { + userOnlineVo = new SystemUserOnlineVo(); + userOnlineVo.setUid(id); + userOnlineVo.setUserName(userJwtVo.getUsername()); + userOnlineVo.setNickName(userJwtVo.getUser().getNickName()); + userOnlineVo.setDept(dept); + userOnlineVo.setBrowser(version); + userOnlineVo.setIp(ip); + userOnlineVo.setAddress(address); + userOnlineVo.setKey(CsDesEncryptUtil.desEncrypt(token)); + userOnlineVo.setLoginTime(new Date()); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + String loginKey = tokenProvider.loginKey(token); + redisHelper.set(loginKey, userOnlineVo, properties.getJwt().getTokenValidityInSeconds(), TimeUnit.MILLISECONDS); + } + + /** + * 退出登录 + * + * @param token / + */ + + public void logoutByToken(String token) { + String loginKey = tokenProvider.loginKey(token); + redisHelper.del(loginKey); + } + + /** + * 导出 + * + * @param all / + * @param response / + * @throws IOException / + */ + + public void downloadUserOnlineModelExcel(List all, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (SystemUserOnlineVo user : all) { + Map map = new LinkedHashMap<>(); + map.put("用户名", user.getUserName()); + map.put("部门", user.getDept()); + map.put("登录IP", user.getIp()); + map.put("登录地点", user.getAddress()); + map.put("浏览器", user.getBrowser()); + map.put("登录日期", user.getLoginTime()); + list.add(map); + } + CsFileUtil.downloadExcel(list, response); + } + + /** + * 根据用户名强退用户 + * + * @param username / + */ + + public void kickOutByUsername(String username) { + String loginKey = SystemRedisKey.ONLINE_USER + username + "*"; + redisHelper.scanDel(loginKey); + } + + /** + * 查询全部数据 + * + * @param onlineVo / + * @param pageable / + * @return / + */ + + public CsPageResult queryUserOnlineModelPage(SystemUserOnlineVo onlineVo, IPage pageable) { + String username = null; + if (onlineVo != null) { + username = onlineVo.getUserName(); + } + List onlineUserList = queryUserOnlineModelListByUsername(username); + List paging = CsPageUtil.softPaging(pageable.getCurrent(), pageable.getSize(), onlineUserList); + return CsPageUtil.toPage(paging, onlineUserList.size()); + } + + /** + * 查询全部数据,不分页 + * + * @param username / + * @return / + */ + + public List queryUserOnlineModelListByUsername(String username) { + String loginKey = SystemRedisKey.ONLINE_USER + (StrUtil.isBlank(username) ? "" : "*" + username); + List keys = redisHelper.scan(loginKey + "*"); + Collections.reverse(keys); + List onlineUserList = new ArrayList<>(); + for (String key : keys) { + onlineUserList.add(redisHelper.get(key, SystemUserOnlineVo.class)); + } + onlineUserList.sort((o1, o2) -> o2.getLoginTime().compareTo(o1.getLoginTime())); + return onlineUserList; + } + + /** + * 查询用户 + * + * @param key / + * @return / + */ + + public SystemUserOnlineVo queryUserOnlineModelByKey(String key) { + return redisHelper.get(key, SystemUserOnlineVo.class); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLock.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLock.java new file mode 100644 index 00000000..043d94d3 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLock.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.bizlock; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** + * 业务锁 + * + * @author odboy + * @date 2025-08-14 + */ +@Inherited +@Documented +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface BizLock { + /** + * 业务名称 + */ + String bizName() default ""; + + /** + * 锁key,支持SpEL表达式 + * 示例: + * #user.id - 获取参数user的id属性 + * #p0 - 获取第一个参数 + * #root.methodName - 获取方法名 + */ + String key() default ""; + + /** + * 锁持有时间(秒),默认60秒。锁的持有时间,超过这个时间锁会自动释放 + */ + long leaseTime() default 60; + + /** + * 时间单位,默认为秒 + */ + TimeUnit timeUnit() default TimeUnit.SECONDS; + + /** + * 获取锁失败后的异常信息 + */ + String errorMessage() default "操作太繁忙,请稍后再试"; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLockAspect.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLockAspect.java new file mode 100644 index 00000000..ebb84b21 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLockAspect.java @@ -0,0 +1,92 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.bizlock; + +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.redis.CsRedisHelper; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + +@Aspect +@Component +public class BizLockAspect { + private final CsRedisHelper redisHelper; + private final ExpressionParser parser = new SpelExpressionParser(); + + public BizLockAspect(CsRedisHelper redisHelper) { + this.redisHelper = redisHelper; + } + + @Around("@annotation(bizLock)") + public Object around(ProceedingJoinPoint joinPoint, BizLock bizLock) throws Throwable { + // 解析SpEL表达式生成最终的lockKey + String lockKey = parseLockKey(joinPoint, bizLock); + Boolean success = redisHelper.setIfAbsent(lockKey, lockKey, bizLock.leaseTime(), bizLock.timeUnit()); + if (success == null || !success) { + throw new BadRequestException(bizLock.errorMessage()); + } + try { + return joinPoint.proceed(); + } catch (Exception e) { + throw e; + } finally { + redisHelper.del(lockKey); + } + } + + private String parseLockKey(ProceedingJoinPoint joinPoint, BizLock bizLock) { + // 如果key为空,使用默认格式: bizName:方法名 + if (bizLock.key().isEmpty()) { + return String.format("%s:%s", bizLock.bizName(), ((MethodSignature) joinPoint.getSignature()).getMethod().getName()); + } + + // 解析SpEL表达式 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + + // 创建SpEL上下文 + StandardEvaluationContext context = new StandardEvaluationContext(); + + // 设置方法参数 + String[] parameterNames = signature.getParameterNames(); + Object[] args = joinPoint.getArgs(); + for (int i = 0; i < parameterNames.length; i++) { + context.setVariable(parameterNames[i], args[i]); + context.setVariable("p" + i, args[i]); // 支持#p0,#p1格式 + } + + // 设置方法相关信息 + context.setVariable("methodName", method.getName()); + context.setVariable("targetClass", joinPoint.getTarget().getClass().getName()); + + // 解析表达式 + Expression expression = parser.parseExpression(bizLock.key()); + String value = expression.getValue(context, String.class); + + // 组合最终的key: bizName:解析后的key + return String.format("%s:%s", bizLock.bizName(), value); + } +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/mybatisplus/MpMetaObjectHandler.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/mybatisplus/MpMetaObjectHandler.java new file mode 100644 index 00000000..d4fe717c --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/mybatisplus/MpMetaObjectHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.mybatisplus; + +import cn.hutool.core.date.DateTime; +import cn.odboy.system.framework.permission.core.CsSecurityHelper; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Component +public class MpMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + DateTime now = DateTime.now(); + /* 创建时间 */ + this.strictInsertFill(metaObject, "createTime", Date.class, now); + this.strictInsertFill(metaObject, "updateTime", Date.class, now); + /* 操作人 */ + String username = "System"; + try { + username = CsSecurityHelper.getCurrentUsername(); + } catch (Exception ignored) { + } + this.strictInsertFill(metaObject, "createBy", String.class, username); + this.strictInsertFill(metaObject, "updateBy", String.class, username); + } + + @Override + public void updateFill(MetaObject metaObject) { + /* 更新时间 */ + this.strictUpdateFill(metaObject, "updateTime", Date.class, DateTime.now()); + /* 操作人 */ + String username = "System"; + try { + username = CsSecurityHelper.getCurrentUsername(); + } catch (Exception ignored) { + } + this.strictUpdateFill(metaObject, "updateBy", String.class, username); + } +} + diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLog.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLog.java new file mode 100644 index 00000000..6de423fe --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLog.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.operalog; + +import java.lang.annotation.*; + +/** + * 捕捉操作日志 + * + * @author odboy + * @date 2025-05-12 + */ +@Inherited +@Documented +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface OperationLog { + String bizName() default ""; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLogAspect.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLogAspect.java new file mode 100644 index 00000000..fad920b9 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLogAspect.java @@ -0,0 +1,120 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.operalog; + +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.framework.context.CsRequestHolder; +import cn.odboy.system.dal.dataobject.SystemOperationLogTb; +import cn.odboy.system.dal.mysql.SystemOperationLogMapper; +import cn.odboy.system.framework.permission.core.CsSecurityHelper; +import cn.odboy.util.CsBrowserUtil; +import cn.odboy.util.CsIPUtil; +import com.alibaba.fastjson2.JSON; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; + +/** + * 捕捉操作日志 + * + * @author odboy + * @date 2025-05-12 + */ +@Slf4j +@Aspect +@Component +public class OperationLogAspect { + @Autowired + private SystemOperationLogMapper systemOperationLogMapper; + + @Around("@annotation(operationLog)") + public Object operationLogCatch(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable { + return handleLog(joinPoint, operationLog); + } + + private Object handleLog(ProceedingJoinPoint joinPoint, OperationLog annotation) throws Throwable { + TimeInterval timeInterval = new TimeInterval(); + try { + Object result = joinPoint.proceed(); + SystemOperationLogTb record = getOperationLogTb(joinPoint, annotation, timeInterval); + ThreadUtil.execAsync(() -> { + try { + systemOperationLogMapper.insert(record); + } catch (Exception e) { + // 忽略 + } + }); + return result; + } catch (Throwable exception) { + SystemOperationLogTb record = getOperationLogTb(joinPoint, annotation, timeInterval); + record.setExceptionDetail(ExceptionUtil.stacktraceToString(exception)); + ThreadUtil.execAsync(() -> { + try { + systemOperationLogMapper.insert(record); + } catch (Exception e) { + // 忽略 + } + }); + throw exception; + } + } + + private SystemOperationLogTb getOperationLogTb(ProceedingJoinPoint joinPoint, OperationLog annotation, TimeInterval timeInterval) { + long executeTime = timeInterval.intervalMs(); + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + String bizName = annotation.bizName(); + if (StrUtil.isBlank(bizName)) { + Method method = signature.getMethod(); + ApiOperation apiOperation = method.getAnnotation(ApiOperation.class); + if (apiOperation != null) { + bizName = apiOperation.value(); + } + } + if (StrUtil.isBlank(bizName)) { + bizName = "默认业务"; + } + String method = joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()"; + Object[] args = joinPoint.getArgs(); + String params = JSON.toJSONString(args); + HttpServletRequest request = CsRequestHolder.getHttpServletRequest(); + String requestIp = CsBrowserUtil.getIp(request); + String browserInfo = CsBrowserUtil.getVersion(request); + String address = CsIPUtil.getCityInfo(requestIp); + String username = CsSecurityHelper.getCurrentUsername(); + SystemOperationLogTb record = new SystemOperationLogTb(); + record.setBizName(bizName); + record.setMethod(method); + record.setParams(params); + record.setRequestIp(requestIp); + record.setExecuteTime(executeTime); + record.setUsername(username); + record.setAddress(address); + record.setBrowserInfo(browserInfo); + return record; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/annotaions/OperationLog.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/annotaions/OperationLog.java new file mode 100644 index 00000000..3fbee830 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/annotaions/OperationLog.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.system.framework.operalog.annotaions; + +import java.lang.annotation.*; + +/** + * 捕捉操作日志 + * + * @author odboy + * @date 2025-05-12 + */ +@Inherited +@Documented +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface OperationLog { + String bizName() default ""; +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/core/SystemOperationLogAspect.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/core/SystemOperationLogAspect.java new file mode 100644 index 00000000..1f82c23c --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/core/SystemOperationLogAspect.java @@ -0,0 +1,122 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.system.framework.operalog.core; + +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.framework.context.CsRequestHolder; +import cn.odboy.system.dal.dataobject.SystemOperationLogTb; +import cn.odboy.system.dal.mysql.SystemOperationLogMapper; +import cn.odboy.system.framework.operalog.annotaions.OperationLog; +import cn.odboy.system.framework.permission.core.CsSecurityHelper; +import cn.odboy.util.CsBrowserUtil; +import cn.odboy.util.CsIPUtil; +import com.alibaba.fastjson2.JSON; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; + +/** + * 捕捉操作日志 + * + * @author odboy + * @date 2025-05-12 + */ +@Slf4j +@Aspect +@Component +public class SystemOperationLogAspect { + @Autowired + private SystemOperationLogMapper systemOperationLogMapper; + + @Around("@annotation(operationLog)") + public Object operationLogCatch(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable { + return handleLog(joinPoint, operationLog); + } + + private Object handleLog(ProceedingJoinPoint joinPoint, OperationLog annotation) throws Throwable { + TimeInterval timeInterval = new TimeInterval(); + try { + Object result = joinPoint.proceed(); + SystemOperationLogTb record = getOperationLogTb(joinPoint, annotation, timeInterval); + ThreadUtil.execAsync(() -> { + try { + systemOperationLogMapper.insert(record); + } catch (Exception e) { + // log.error("保存审计日志失败", e); + // 忽略 + } + }); + return result; + } catch (Throwable exception) { + SystemOperationLogTb record = getOperationLogTb(joinPoint, annotation, timeInterval); + record.setExceptionDetail(ExceptionUtil.stacktraceToString(exception)); + ThreadUtil.execAsync(() -> { + try { + systemOperationLogMapper.insert(record); + } catch (Exception e) { + // log.error("保存审计日志失败", e); + // 忽略 + } + }); + throw exception; + } + } + + private SystemOperationLogTb getOperationLogTb(ProceedingJoinPoint joinPoint, OperationLog annotation, TimeInterval timeInterval) { + long executeTime = timeInterval.intervalMs(); + MethodSignature signature = (MethodSignature)joinPoint.getSignature(); + String bizName = annotation.bizName(); + if (StrUtil.isBlank(bizName)) { + Method method = signature.getMethod(); + ApiOperation apiOperation = method.getAnnotation(ApiOperation.class); + if (apiOperation != null) { + bizName = apiOperation.value(); + } + } + if (StrUtil.isBlank(bizName)) { + bizName = "默认业务"; + } + String method = joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()"; + Object[] args = joinPoint.getArgs(); + String params = JSON.toJSONString(args); + HttpServletRequest request = CsRequestHolder.getHttpServletRequest(); + String requestIp = CsBrowserUtil.getIp(request); + String browserInfo = CsBrowserUtil.getVersion(request); + String address = CsIPUtil.getCityInfo(requestIp); + String username = CsSecurityHelper.getCurrentUsername(); + SystemOperationLogTb record = new SystemOperationLogTb(); + record.setBizName(bizName); + record.setMethod(method); + record.setParams(params); + record.setRequestIp(requestIp); + record.setExecuteTime(executeTime); + record.setUsername(username); + record.setAddress(address); + record.setBrowserInfo(browserInfo); + return record; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/AuthorityConfig.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/AuthorityConfig.java new file mode 100644 index 00000000..1b383759 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/AuthorityConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.permission.config; + +import cn.odboy.system.framework.permission.core.CsSecurityHelper; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Service(value = "el") +public class AuthorityConfig { + + /** + * 判断接口是否有权限 + * + * @param permissions 权限 + * @return / + */ + public Boolean check(String... permissions) { + // 获取当前用户的所有权限 + List roleList = CsSecurityHelper.getCurrentUser().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()); + // 判断当前用户的所有权限是否包含接口上定义的权限 + return roleList.contains("admin") || Arrays.stream(permissions).anyMatch(roleList::contains); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/SpringSecurityConfig.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/SpringSecurityConfig.java new file mode 100644 index 00000000..5de7b226 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/SpringSecurityConfig.java @@ -0,0 +1,113 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.permission.config; + +import cn.odboy.constant.RequestMethodEnum; +import cn.odboy.system.dal.redis.SystemUserOnlineInfoDAO; +import cn.odboy.system.framework.permission.core.handler.JwtAccessDeniedHandler; +import cn.odboy.system.framework.permission.core.handler.JwtAuthenticationEntryPoint; +import cn.odboy.system.framework.permission.core.handler.TokenProvider; +import cn.odboy.util.CsAnonTagUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.core.GrantedAuthorityDefaults; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.filter.CorsFilter; + +import java.util.Map; +import java.util.Set; + +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class SpringSecurityConfig { + @Autowired + private TokenProvider tokenProvider; + @Autowired + private CorsFilter corsFilter; + @Autowired + private JwtAuthenticationEntryPoint authenticationErrorHandler; + @Autowired + private JwtAccessDeniedHandler jwtAccessDeniedHandler; + @Autowired + private ApplicationContext applicationContext; + @Autowired + private SystemUserOnlineInfoDAO systemUserOnlineInfoDAO; + + @Bean + public GrantedAuthorityDefaults grantedAuthorityDefaults() { + // 去除 ROLE_ 前缀 + return new GrantedAuthorityDefaults(""); + } + + @Bean + public PasswordEncoder passwordEncoder() { + // 密码加密方式 + return new BCryptPasswordEncoder(); + } + + @Bean + protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { + // 获取匿名标记 + Map> anonymousUrls = CsAnonTagUtil.getAnonymousUrl(applicationContext); + return httpSecurity + // 禁用 CSRF + .csrf().disable().addFilter(corsFilter) + // 授权异常 + .exceptionHandling().authenticationEntryPoint(authenticationErrorHandler).accessDeniedHandler(jwtAccessDeniedHandler) + // 防止iframe 造成跨域 + .and().headers().frameOptions().disable() + // 不创建会话 + .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests() + // 静态资源等等 + .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/websocket/**").permitAll() + // swagger 文档 + .antMatchers("/swagger-ui.html").permitAll().antMatchers("/swagger-resources/**").permitAll().antMatchers("/webjars/**").permitAll().antMatchers("/*/api-docs").permitAll() + // 文件 + .antMatchers("/avatar/**").permitAll().antMatchers("/file/**").permitAll() + // 阿里巴巴 druid + .antMatchers("/druid/**").permitAll() + // 放行OPTIONS请求 + .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() + // 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型 + // GET + .antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll() + // POST + .antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll() + // PUT + .antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll() + // PATCH + .antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll() + // DELETE + .antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll() + // 所有类型的接口都放行 + .antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll() + // 所有请求都需要认证 + .anyRequest().authenticated().and().apply(securityConfigurerAdapter()).and().build(); + } + + private TokenConfigurer securityConfigurerAdapter() { + return new TokenConfigurer(tokenProvider, systemUserOnlineInfoDAO); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/TokenConfigurer.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/TokenConfigurer.java new file mode 100644 index 00000000..e9b73e22 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/TokenConfigurer.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.permission.config; + +import cn.odboy.system.dal.redis.SystemUserOnlineInfoDAO; +import cn.odboy.system.framework.permission.core.handler.TokenFilter; +import cn.odboy.system.framework.permission.core.handler.TokenProvider; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +public class TokenConfigurer extends SecurityConfigurerAdapter { + private final TokenProvider tokenProvider; + private final SystemUserOnlineInfoDAO systemUserOnlineInfoDAO; + + public TokenConfigurer(TokenProvider tokenProvider, SystemUserOnlineInfoDAO systemUserOnlineInfoDAO) { + this.tokenProvider = tokenProvider; + this.systemUserOnlineInfoDAO = systemUserOnlineInfoDAO; + } + + @Override + public void configure(HttpSecurity http) { + TokenFilter customFilter = new TokenFilter(tokenProvider, systemUserOnlineInfoDAO); + http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/CsSecurityHelper.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/CsSecurityHelper.java new file mode 100644 index 00000000..e3894bbd --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/CsSecurityHelper.java @@ -0,0 +1,135 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.permission.core; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.jwt.JWT; +import cn.hutool.jwt.JWTUtil; +import cn.odboy.constant.SystemConst; +import cn.odboy.framework.context.CsSpringBeanHolder; +import cn.odboy.system.constant.SystemDataScopeEnum; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Objects; + +/** + * 获取当前登录的用户 + */ +@Slf4j +@Component +public class CsSecurityHelper { + /** + * 获取当前登录的用户 + * + * @return UserDetails + */ + public static UserDetails getCurrentUser() { + UserDetailsService userDetailsService = CsSpringBeanHolder.getBean(UserDetailsService.class); + return userDetailsService.loadUserByUsername(getCurrentUsername()); + } + + /** + * 获取当前用户的数据权限 + * + * @return / + */ + public static List getCurrentUserDataScope() { + UserDetails userDetails = getCurrentUser(); + // 将 Java 对象转换为 JSONObject 对象 + JSONObject jsonObject = (JSONObject) JSON.toJSON(userDetails); + JSONArray jsonArray = jsonObject.getJSONArray("dataScopes"); + return JSON.parseArray(jsonArray.toJSONString(), Long.class); + } + + /** + * 获取数据权限级别 + * + * @return 级别 + */ + public static String getDataScopeType() { + List dataScopes = getCurrentUserDataScope(); + if (CollUtil.isEmpty(dataScopes)) { + return ""; + } + return SystemDataScopeEnum.ALL.getValue(); + } + + /** + * 获取用户ID + * + * @return 系统用户ID + */ + public static Long getCurrentUserId() { + return getCurrentUserId(getToken()); + } + + /** + * 获取用户ID + * + * @return 系统用户ID + */ + public static Long getCurrentUserId(String token) { + JWT jwt = JWTUtil.parseToken(token); + return Long.valueOf(jwt.getPayload("userId").toString()); + } + + /** + * 获取系统用户名称 + * + * @return 系统用户名称 + */ + public static String getCurrentUsername() { + return getCurrentUsername(getToken()); + } + + /** + * 获取系统用户名称 + * + * @return 系统用户名称 + */ + public static String getCurrentUsername(String token) { + JWT jwt = JWTUtil.parseToken(token); + return jwt.getPayload("sub").toString(); + } + + /** + * 获取Token + * + * @return / + */ + public static String getToken() { + HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); + String bearerToken = request.getHeader(SystemConst.HEADER_NAME); + if (bearerToken != null && bearerToken.startsWith(SystemConst.TOKEN_PREFIX)) { + // 去掉令牌前缀 + return bearerToken.replace(SystemConst.TOKEN_PREFIX, ""); + } else { + log.debug("非法Token:{}", bearerToken); + } + return null; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAccessDeniedHandler.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAccessDeniedHandler.java new file mode 100644 index 00000000..f84b563f --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAccessDeniedHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.permission.core.handler; + +import cn.odboy.framework.exception.handler.ApiError; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class JwtAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { + //当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应 + response.setStatus(HttpStatus.FORBIDDEN.value()); + response.setContentType("application/json;charset=UTF-8"); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonResponse = objectMapper.writeValueAsString(ApiError.error(HttpStatus.FORBIDDEN.value(), "禁止访问,您没有权限访问此资源")); + response.getWriter().write(jsonResponse); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAuthenticationEntryPoint.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAuthenticationEntryPoint.java new file mode 100644 index 00000000..09cc25d3 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAuthenticationEntryPoint.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.permission.core.handler; + +import cn.odboy.framework.exception.handler.ApiError; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Slf4j +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { + // 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应 + int code = HttpStatus.UNAUTHORIZED.value(); + response.setStatus(code); + response.setContentType("application/json;charset=UTF-8"); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonResponse = objectMapper.writeValueAsString(ApiError.error(HttpStatus.UNAUTHORIZED.value(), "登录状态已过期,请重新登录")); + response.getWriter().write(jsonResponse); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenFilter.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenFilter.java new file mode 100644 index 00000000..8916de07 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenFilter.java @@ -0,0 +1,87 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.permission.core.handler; + +import cn.hutool.core.util.StrUtil; +import cn.odboy.constant.SystemConst; +import cn.odboy.system.dal.model.SystemUserOnlineVo; +import cn.odboy.system.dal.redis.SystemUserOnlineInfoDAO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +@Slf4j +public class TokenFilter extends GenericFilterBean { + private final TokenProvider tokenProvider; + private final SystemUserOnlineInfoDAO systemUserOnlineInfoDAO; + + /** + * @param tokenProvider Token + * @param systemUserOnlineInfoDAO 用户在线 + */ + public TokenFilter(TokenProvider tokenProvider, SystemUserOnlineInfoDAO systemUserOnlineInfoDAO) { + this.systemUserOnlineInfoDAO = systemUserOnlineInfoDAO; + this.tokenProvider = tokenProvider; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException { + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + String token = resolveToken(httpServletRequest); + // 对于 Token 为空的不需要去查 Redis + if (StrUtil.isNotBlank(token)) { + // 获取用户Token的Key + String loginKey = tokenProvider.loginKey(token); + SystemUserOnlineVo userOnlineVo = systemUserOnlineInfoDAO.queryUserOnlineModelByKey(loginKey); + // 判断用户在线信息是否为空 + if (userOnlineVo != null) { + // Token 续期判断 + tokenProvider.checkRenewal(token); + // 获取认证信息,设置上下文 + Authentication authentication = tokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + filterChain.doFilter(servletRequest, servletResponse); + } + + /** + * 初步检测Token + * + * @param request / + * @return / + */ + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader(SystemConst.HEADER_NAME); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(SystemConst.TOKEN_PREFIX)) { + // 去掉令牌前缀 + return bearerToken.replace(SystemConst.TOKEN_PREFIX + " ", ""); + } else { + log.debug("非法Token:{}", bearerToken); + } + return null; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenProvider.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenProvider.java new file mode 100644 index 00000000..8e502fd4 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenProvider.java @@ -0,0 +1,167 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.permission.core.handler; + +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.IdUtil; +import cn.odboy.constant.SystemConst; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.redis.CsRedisHelper; +import cn.odboy.system.dal.model.SystemUserJwtVo; +import cn.odboy.system.dal.redis.SystemCacheKey; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtParser; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.User; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +public class TokenProvider implements InitializingBean { + @Autowired + private CsRedisHelper redisHelper; + @Autowired + private AppProperties properties; + // 0.11.x版本 + // private Key signingKey; + // 0.12.x版本 + private SecretKey signingKey; + private JwtParser jwtParser; + public static final String AUTHORITIES_UUID_KEY = "uid"; + public static final String AUTHORITIES_UID_KEY = "userId"; + + @Override + public void afterPropertiesSet() { + // 解码Base64密钥并创建签名密钥 + byte[] keyBytes = Decoders.BASE64.decode(properties.getJwt().getBase64Secret()); + signingKey = Keys.hmacShaKeyFor(keyBytes); + // 0.11.x版本 + // jwtParser = Jwts.parserBuilder() + // // 使用预生成的签名密钥 + // .setSigningKey(signingKey).build(); + // 0.12.x版本 + jwtParser = Jwts.parser().verifyWith(signingKey).build(); + } + + /** + * 创建Token 设置永不过期, Token 的时间有效性转到Redis 维护 + * + * @param user / + * @return / + */ + public String createToken(SystemUserJwtVo user) { + // 设置参数 + Map claims = new HashMap<>(6); + // 设置用户ID + claims.put(AUTHORITIES_UID_KEY, user.getUser().getId()); + // 设置UUID,确保每次Token不一样 + claims.put(AUTHORITIES_UUID_KEY, IdUtil.simpleUUID()); + // 0.11.x版本 + // 直接调用 Jwts.builder() 创建新实例Add commentMore actions + // return Jwts.builder() + // // 设置自定义 Claims + // .setClaims(claims) + // // 设置主题 + // .setSubject(user.getUsername()) + // // 使用预生成的签名密钥和算法签名 + // .signWith(signingKey, SignatureAlgorithm.HS512).compact(); + // 0.12.x版本 + return Jwts.builder().claims(claims).subject(user.getUsername()).signWith(signingKey).compact(); + } + + /** + * 依据Token 获取鉴权信息 + * + * @param token / + * @return / + */ + Authentication getAuthentication(String token) { + Claims claims = getClaims(token); + User principal = new User(claims.getSubject(), "******", new ArrayList<>()); + return new UsernamePasswordAuthenticationToken(principal, token, new ArrayList<>()); + } + + public Claims getClaims(String token) { + // 0.11.x版本 + // return jwtParser.parseClaimsJws(token).getBody(); + // 0.12.x版本 + return jwtParser.parseSignedClaims(token).getPayload(); + } + + /** + * @param token 需要检查的token + */ + public void checkRenewal(String token) { + // 判断是否续期token,计算token的过期时间 + String loginKey = loginKey(token); + long time = redisHelper.getExpire(loginKey) * 1000; + Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time); + // 判断当前时间与过期时间的时间差 + long differ = expireDate.getTime() - System.currentTimeMillis(); + // 如果在续期检查的范围内,则续期 + if (differ <= properties.getJwt().getDetect()) { + long renew = time + properties.getJwt().getRenew(); + redisHelper.expire(loginKey, renew, TimeUnit.MILLISECONDS); + } + } + + public String getToken(HttpServletRequest request) { + final String requestHeader = request.getHeader(SystemConst.HEADER_NAME); + if (requestHeader != null && requestHeader.startsWith(SystemConst.TOKEN_PREFIX)) { + return requestHeader.substring(7); + } + return null; + } + + /** + * 获取登录用户RedisKey + * + * @param token / + * @return key + */ + public String loginKey(String token) { + Claims claims = getClaims(token); + return SystemCacheKey.ONLINE_USER + claims.getSubject() + ":" + getId(token); + } + + /** + * 获取会话编号 + * + * @param token / + * @return / + */ + public String getId(String token) { + Claims claims = getClaims(token); + return claims.get(AUTHORITIES_UUID_KEY, String.class); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/UserDetailsHandler.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/UserDetailsHandler.java new file mode 100644 index 00000000..90969e82 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/UserDetailsHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.permission.core.handler; + +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.system.dal.dataobject.SystemUserTb; +import cn.odboy.system.dal.model.SystemRoleCodeVo; +import cn.odboy.system.dal.model.SystemUserJwtVo; +import cn.odboy.system.dal.redis.SystemUserInfoDAO; +import cn.odboy.system.service.SystemDataService; +import cn.odboy.system.service.SystemRoleService; +import cn.odboy.system.service.SystemUserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * TODO 通过用户名判断用户信息,并获取拥有的权限与部门 + */ +@Slf4j +@Service("userDetailsService") +public class UserDetailsHandler implements UserDetailsService { + @Autowired + private SystemRoleService systemRoleService; + @Autowired + private SystemUserService systemUserService; + @Autowired + private SystemDataService systemDataService; + @Autowired + private SystemUserInfoDAO systemUserInfoDAO; + + @Override + public SystemUserJwtVo loadUserByUsername(String username) { + SystemUserJwtVo userJwtVo = systemUserInfoDAO.getUserLoginInfoByUserName(username); + if (userJwtVo == null) { + SystemUserTb user = systemUserService.getUserByUsername(username); + if (user == null) { + throw new BadRequestException("用户不存在"); + } else { + if (!user.getEnabled()) { + throw new BadRequestException("账号未激活"); + } + // 获取用户的权限 + List authorities = systemRoleService.buildUserRolePermissions(user); + // 初始化JwtUserDto + userJwtVo = new SystemUserJwtVo(user, systemDataService.queryDeptIdListByArgs(user), authorities); + // 添加缓存数据 + systemUserInfoDAO.saveUserLoginInfoByUserName(username, userJwtVo); + } + } + return userJwtVo; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ActiveJobRegister.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ActiveJobRegister.java new file mode 100644 index 00000000..a40040d9 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ActiveJobRegister.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.quartz; + +import cn.odboy.system.dal.dataobject.SystemQuartzJobTb; +import cn.odboy.system.dal.mysql.SystemQuartzJobMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +public class ActiveJobRegister implements ApplicationRunner { + @Autowired + private SystemQuartzJobMapper quartzJobMapper; + @Autowired + private QuartzManage quartzManage; + + /** + * 项目启动时重新激活启用的定时任务 + * + * @param applicationArguments / + */ + @Override + public void run(ApplicationArguments applicationArguments) { + List quartzJobs = quartzJobMapper.selectAllEnableQuartzJob(); + quartzJobs.forEach(quartzManage::addJob); + log.info("Timing task injection complete"); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ExecutionJobBean.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ExecutionJobBean.java new file mode 100644 index 00000000..2cf02c53 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ExecutionJobBean.java @@ -0,0 +1,133 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.quartz; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.template.Template; +import cn.hutool.extra.template.TemplateConfig; +import cn.hutool.extra.template.TemplateEngine; +import cn.hutool.extra.template.TemplateUtil; +import cn.odboy.framework.context.CsSpringBeanHolder; +import cn.odboy.framework.quartz.core.QuartzRunnable; +import cn.odboy.framework.redis.CsRedisHelper; +import cn.odboy.system.dal.dataobject.SystemQuartzJobTb; +import cn.odboy.system.dal.dataobject.SystemQuartzLogTb; +import cn.odboy.system.dal.model.SystemSendEmailArgs; +import cn.odboy.system.dal.mysql.SystemQuartzLogMapper; +import cn.odboy.system.service.SystemEmailService; +import cn.odboy.system.service.SystemQuartzJobService; +import cn.odboy.util.CsStringUtil; +import lombok.extern.slf4j.Slf4j; +import org.quartz.JobExecutionContext; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.scheduling.quartz.QuartzJobBean; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Future; + +@Slf4j +public class ExecutionJobBean extends QuartzJobBean { + /** + * 此处仅供参考,可根据任务执行情况自定义线程池参数 + */ + private final ThreadPoolTaskExecutor executor = CsSpringBeanHolder.getBean("taskAsync"); + + @Override + public void executeInternal(JobExecutionContext context) { + // 获取任务 + SystemQuartzJobTb quartzJob = (SystemQuartzJobTb) context.getMergedJobDataMap().get(SystemQuartzJobTb.JOB_KEY); + // 获取spring bean + SystemQuartzLogMapper quartzLogMapper = CsSpringBeanHolder.getBean(SystemQuartzLogMapper.class); + SystemQuartzJobService systemQuartzJobService = CsSpringBeanHolder.getBean(SystemQuartzJobService.class); + CsRedisHelper redisHelper = CsSpringBeanHolder.getBean(CsRedisHelper.class); + + String uuid = quartzJob.getUuid(); + + SystemQuartzLogTb quartzLog = new SystemQuartzLogTb(); + quartzLog.setJobName(quartzJob.getJobName()); + quartzLog.setBeanName(quartzJob.getBeanName()); + quartzLog.setMethodName(quartzJob.getMethodName()); + quartzLog.setParams(quartzJob.getParams()); + long startTime = System.currentTimeMillis(); + quartzLog.setCronExpression(quartzJob.getCronExpression()); + try { + // 执行任务 + QuartzRunnable task = new QuartzRunnable(quartzJob.getBeanName(), quartzJob.getMethodName(), quartzJob.getParams()); + Future future = executor.submit(task); + // 忽略任务执行结果 + future.get(); + long times = System.currentTimeMillis() - startTime; + quartzLog.setTime(times); + if (StrUtil.isNotBlank(uuid)) { + redisHelper.set(uuid, true); + } + // 任务状态 + quartzLog.setIsSuccess(true); + log.info("任务执行成功,任务名称:{}, 执行时间:{}毫秒", quartzJob.getJobName(), times); + // 判断是否存在子任务 + if (StrUtil.isNotBlank(quartzJob.getSubTask())) { + String[] tasks = quartzJob.getSubTask().split("[,,]"); + // 执行子任务 + systemQuartzJobService.startSubQuartJob(tasks); + } + } catch (Exception e) { + if (StrUtil.isNotBlank(uuid)) { + redisHelper.set(uuid, false); + } + log.error("任务执行失败,任务名称:{}", quartzJob.getJobName(), e); + long times = System.currentTimeMillis() - startTime; + quartzLog.setTime(times); + // 任务状态 0:成功 1:失败 + quartzLog.setIsSuccess(false); + quartzLog.setExceptionDetail(ExceptionUtil.stacktraceToString(e)); + // 任务如果失败了则暂停 + if (quartzJob.getPauseAfterFailure() != null && quartzJob.getPauseAfterFailure()) { + quartzJob.setIsPause(false); + // 更新状态 + systemQuartzJobService.switchQuartzJobStatus(quartzJob); + } + if (quartzJob.getEmail() != null) { + SystemEmailService emailService = CsSpringBeanHolder.getBean(SystemEmailService.class); + // 邮箱报警 + if (StrUtil.isNotBlank(quartzJob.getEmail())) { + SystemSendEmailArgs sendEmailRequest = taskAlarm(quartzJob, ExceptionUtil.stacktraceToString(e)); + emailService.sendEmail(sendEmailRequest); + } + } + } finally { + quartzLogMapper.insert(quartzLog); + } + } + + private SystemSendEmailArgs taskAlarm(SystemQuartzJobTb quartzJob, String msg) { + SystemSendEmailArgs sendEmailRequest = new SystemSendEmailArgs(); + sendEmailRequest.setSubject("定时任务【" + quartzJob.getJobName() + "】执行失败,请尽快处理!"); + Map data = new HashMap<>(16); + data.put("task", quartzJob); + data.put("msg", msg); + TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH)); + Template template = engine.getTemplate("SystemQuartzJobTaskAlarmTemplate.ftl"); + sendEmailRequest.setContent(template.render(data)); + List emails = Arrays.asList(quartzJob.getEmail().split("[,,]")); + sendEmailRequest.setTos(emails); + return sendEmailRequest; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/QuartzManage.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/QuartzManage.java new file mode 100644 index 00000000..529df9c9 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/QuartzManage.java @@ -0,0 +1,173 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.quartz; + +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.system.dal.dataobject.SystemQuartzJobTb; +import lombok.extern.slf4j.Slf4j; +import org.quartz.*; +import org.quartz.impl.triggers.CronTriggerImpl; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; + +@Slf4j +@Component +public class QuartzManage { + + private static final String JOB_NAME = "TASK_"; + + @Resource + private Scheduler scheduler; + + public void addJob(SystemQuartzJobTb quartzJob) { + try { + // 构建job信息 + JobDetail jobDetail = JobBuilder.newJob(ExecutionJobBean.class).withIdentity(JOB_NAME + quartzJob.getId()).build(); + + // 通过触发器名和cron 表达式创建 Trigger + Trigger trigger = TriggerBuilder.newTrigger().withIdentity(JOB_NAME + quartzJob.getId()).startNow().withSchedule(CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression())).build(); + + trigger.getJobDataMap().put(SystemQuartzJobTb.JOB_KEY, quartzJob); + + // 重置启动时间 + ((CronTriggerImpl) trigger).setStartTime(new Date()); + + // 执行定时任务,如果是持久化的,这里会报错,捕获输出 + try { + scheduler.scheduleJob(jobDetail, trigger); + } catch (ObjectAlreadyExistsException e) { + log.warn("定时任务已存在,跳过加载"); + } + + // 暂停任务 + if (quartzJob.getIsPause()) { + pauseJob(quartzJob); + } + } catch (Exception e) { + log.error("创建定时任务失败", e); + throw new BadRequestException("创建定时任务失败"); + } + } + + /** + * 更新job cron表达式 + * + * @param quartzJob / + */ + public void updateJobCron(SystemQuartzJobTb quartzJob) { + try { + TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId()); + CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); + // 如果不存在则创建一个定时任务 + if (trigger == null) { + addJob(quartzJob); + trigger = (CronTrigger) scheduler.getTrigger(triggerKey); + } + CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression()); + trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); + // 重置启动时间 + ((CronTriggerImpl) trigger).setStartTime(new Date()); + trigger.getJobDataMap().put(SystemQuartzJobTb.JOB_KEY, quartzJob); + + scheduler.rescheduleJob(triggerKey, trigger); + // 暂停任务 + if (quartzJob.getIsPause()) { + pauseJob(quartzJob); + } + } catch (Exception e) { + log.error("更新定时任务失败", e); + throw new BadRequestException("更新定时任务失败"); + } + } + + /** + * 删除一个job + * + * @param quartzJob / + */ + public void deleteJob(SystemQuartzJobTb quartzJob) { + try { + JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId()); + scheduler.pauseJob(jobKey); + scheduler.deleteJob(jobKey); + } catch (Exception e) { + log.error("删除定时任务失败", e); + throw new BadRequestException("删除定时任务失败"); + } + } + + /** + * 恢复一个job + * + * @param quartzJob / + */ + public void resumeJob(SystemQuartzJobTb quartzJob) { + try { + TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId()); + CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); + // 如果不存在则创建一个定时任务 + if (trigger == null) { + addJob(quartzJob); + } + JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId()); + scheduler.resumeJob(jobKey); + } catch (Exception e) { + log.error("恢复定时任务失败", e); + throw new BadRequestException("恢复定时任务失败"); + } + } + + /** + * 立即执行job + * + * @param quartzJob / + */ + public void runJobNow(SystemQuartzJobTb quartzJob) { + try { + TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId()); + CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); + // 如果不存在则创建一个定时任务 + if (trigger == null) { + addJob(quartzJob); + } + JobDataMap dataMap = new JobDataMap(); + dataMap.put(SystemQuartzJobTb.JOB_KEY, quartzJob); + JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId()); + scheduler.triggerJob(jobKey, dataMap); + } catch (Exception e) { + log.error("定时任务执行失败", e); + throw new BadRequestException("定时任务执行失败"); + } + } + + /** + * 暂停一个job + * + * @param quartzJob / + */ + public void pauseJob(SystemQuartzJobTb quartzJob) { + try { + JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId()); + scheduler.pauseJob(jobKey); + } catch (Exception e) { + log.error("定时任务暂停失败", e); + throw new BadRequestException("定时任务暂停失败"); + } + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/OssStorageConfiguration.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/OssStorageConfiguration.java new file mode 100644 index 00000000..7d09ce44 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/OssStorageConfiguration.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.storage; + +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.properties.model.OSSConfigModel; +import io.minio.MinioClient; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Slf4j +@Configuration +public class OssStorageConfiguration { + @Autowired + private AppProperties properties; + + @Bean + public MinioClient minioClient() { + try { + OSSConfigModel minio = properties.getOss().getMinio(); + return MinioClient.builder().endpoint(minio.getEndpoint()).credentials(minio.getAccessKey(), minio.getSecretKey()).build(); + } catch (Exception e) { + log.error("创建Minio客户端失败", e); + throw new BadRequestException("创建Minio客户端失败"); + } + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/local/MultipartConfig.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/local/MultipartConfig.java new file mode 100644 index 00000000..4a3bc6ee --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/local/MultipartConfig.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.storage.local; + +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.properties.AppProperties; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.MultipartConfigFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.unit.DataSize; + +import javax.servlet.MultipartConfigElement; +import java.io.File; + +@Slf4j +@Configuration +public class MultipartConfig { + @Value("${spring.application.name}") + private String name; + @Autowired + private AppProperties properties; + + /** + * 文件上传临时路径 + */ + @Bean + public MultipartConfigElement multipartConfigElement() { + MultipartConfigFactory factory = new MultipartConfigFactory(); + factory.setMaxFileSize(DataSize.ofMegabytes(properties.getOss().getMaxSize())); + factory.setMaxRequestSize(DataSize.ofMegabytes(properties.getOss().getMaxSize())); + String location = System.getProperty("user.home") + "/" + name + "/tmp"; + File tmpFile = new File(location); + if (!tmpFile.exists()) { + if (!tmpFile.mkdirs()) { + log.error("创建临时文件失败"); + throw new BadRequestException("创建临时文件失败"); + } + } + factory.setLocation(location); + return factory.createMultipartConfig(); + } +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/minio/MinioRepository.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/minio/MinioRepository.java new file mode 100644 index 00000000..111c9a37 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/minio/MinioRepository.java @@ -0,0 +1,363 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.framework.storage.minio; + +import cn.hutool.core.io.FastByteArrayOutputStream; +import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.UUID; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.properties.model.OSSConfigModel; +import cn.odboy.framework.properties.model.StorageOSSModel; +import cn.odboy.system.dal.dataobject.SystemOssStorageTb; +import cn.odboy.util.CsDateUtil; +import cn.odboy.util.CsFileUtil; +import io.minio.*; +import io.minio.http.Method; +import io.minio.messages.Bucket; +import io.minio.messages.Item; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Slf4j +@Component +public class MinioRepository { + @Autowired + private AppProperties properties; + @Autowired + private MinioClient minioClient; + + /** + * 检查存储bucket是否存在 + * + * @param bucketName Bucket 名称 + * @return boolean 存在返回 true,否则返回 false + */ + public boolean existsBucket(String bucketName) { + try { + return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); + } catch (Exception e) { + log.error("检查 bucket 是否存在失败,bucketName: {}, 错误信息", bucketName, e); + return false; + } + } + + /** + * 创建存储bucket + * + * @param bucketName Bucket 名称 + * @return boolean 创建成功返回 true,否则返回 false + */ + public boolean createBucket(String bucketName) { + try { + if (!existsBucket(bucketName)) { + minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); + log.info("Bucket 创建成功,bucketName: {}", bucketName); + return true; + } else { + log.warn("Bucket 已经存在,bucketName: {}", bucketName); + } + } catch (Exception e) { + log.error("创建 bucket 失败,bucketName: {}, 错误信息", bucketName, e); + } + return false; + } + + /** + * 删除存储bucket + * + * @param bucketName Bucket 名称 + * @return boolean 删除成功返回 true,否则返回 false + */ + public boolean removeBucket(String bucketName) { + try { + if (existsBucket(bucketName)) { + minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); + log.info("Bucket 删除成功,bucketName: {}", bucketName); + return true; + } else { + log.warn("Bucket 不存在,bucketName: {}", bucketName); + } + } catch (Exception e) { + log.error("删除 bucket 失败,bucketName: {}, 错误信息", bucketName, e); + } + return false; + } + + /** + * 获取所有 bucket 列表 + * + * @return List 所有的 Bucket 列表 + */ + public List queryAllBuckets() { + try { + return minioClient.listBuckets(); + } catch (Exception e) { + log.error("获取所有 buckets 失败,错误信息", e); + } + return Collections.emptyList(); + } + + /** + * 文件上传 + * + * @param tempFile 文件 + * @param originalFilename 源文件名 + * @param fileSize 文件大小 + * @param contentType 文件类型 + * @param md5 文件md5 + * @return String 上传后的文件名,上传失败返回 null + */ + public SystemOssStorageTb upload(File tempFile, String originalFilename, long fileSize, String contentType, String md5) { + StorageOSSModel ossConfig = properties.getOss(); + OSSConfigModel ossMinioConfig = ossConfig.getMinio(); + String type = FileTypeUtil.getType(tempFile); + String prefix = CsFileUtil.getPrefix(tempFile); + String suffix = CsFileUtil.getSuffix(tempFile); + + String fileCode = IdUtil.fastSimpleUUID(); + String fileName = fileCode + FileUtil.getSuffix(originalFilename); + String objectName = CsDateUtil.getNowDateStr() + "/" + fileName; + log.info("上传文件,原文件名:{},上传后文件名:{}", originalFilename, fileName); + String fileUrl = ossMinioConfig.getEndpoint() + "/" + ossMinioConfig.getBucketName() + "/" + objectName; + + SystemOssStorageTb systemOssStorageTb = new SystemOssStorageTb(); + systemOssStorageTb.setServiceType("minio"); + systemOssStorageTb.setEndpoint(ossMinioConfig.getEndpoint()); + systemOssStorageTb.setBucketName(StrUtil.trim(ossMinioConfig.getBucketName())); + systemOssStorageTb.setFileName(originalFilename); + systemOssStorageTb.setFileSize(fileSize); + systemOssStorageTb.setFileMime(type); + systemOssStorageTb.setFilePrefix(prefix); + systemOssStorageTb.setFileSuffix(suffix); + systemOssStorageTb.setFileMd5(md5); + systemOssStorageTb.setFileUrl(fileUrl); + systemOssStorageTb.setFileCode(fileCode); + systemOssStorageTb.setObjectName(objectName); + + try (InputStream fileInputStream = CsFileUtil.getInputStream(tempFile)) { + PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(ossMinioConfig.getBucketName()).object(objectName).stream(fileInputStream, fileSize, -1).contentType(contentType).build(); + minioClient.putObject(objectArgs); + log.info("文件上传成功,并删除临时文件,文件名:{}", originalFilename); + return systemOssStorageTb; + } catch (Exception e) { + log.error("文件上传失败,文件名:{},错误信息", originalFilename, e); + } finally { + try { + CsFileUtil.del(tempFile); + } catch (IORuntimeException e) { + // 删除失败忽略 + } + } + return null; + } + + /** + * 优化文件上传 文件上传:优化版 + * + * @param file + * @return {@link String } + */ + public String optimizedUpload(MultipartFile file) { + String originalFilename = file.getOriginalFilename(); + if (StrUtil.isBlank(originalFilename)) { + throw new BadRequestException("文件名不能为空"); + } + + long size = file.getSize(); + CsFileUtil.checkSize(properties.getOss().getMaxSize(), size); + + String fileName = UUID.randomUUID() + FileUtil.getSuffix(originalFilename); + // yyyy/MM/dd/fileName.xxx + String objectName = CsDateUtil.getNowDateTimeStr() + "/" + fileName; + + log.info("准备上传文件,原文件名:{},上传后文件名:{}", originalFilename, fileName); + try (InputStream fileInputStream = file.getInputStream()) { + PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(properties.getOss().getMinio().getBucketName()).object(objectName).stream(fileInputStream, file.getSize(), -1).contentType(file.getContentType()).build(); + // 使用重试机制进行上传 + uploadWithRetry(objectArgs, 3); + log.info("文件上传成功,objectName: {}", objectName); + return objectName; + } catch (Exception e) { + log.error("文件上传失败,文件名:{},错误信息", originalFilename, e); + } + return null; + } + + /** + * 上传重试机制 + * + * @param objectArgs 对象参数 + * @param maxRetries 最大重试次数 + * @throws Exception 例外 + */ + private void uploadWithRetry(PutObjectArgs objectArgs, int maxRetries) throws Exception { + int attempt = 0; + while (attempt < maxRetries) { + try { + minioClient.putObject(objectArgs); + return; // 上传成功,返回 + } catch (Exception e) { + attempt++; + log.warn("上传失败,第 {} 次尝试,错误信息", attempt, e); + if (attempt == maxRetries) { + throw new BadRequestException("上传失败,已达到最大重试次数"); + } + // 等待一段时间后重试 + Thread.sleep(2000); + } + } + } + + /** + * 生成文件的预签名 URL,用于文件预览 + * + * @param fullFileName 文件完整路径 + * @return String 预签名 URL + */ + public String generatePreviewUrl(String fullFileName) { + if (StrUtil.isBlank(fullFileName)) { + log.warn("文件名为空,无法生成预签名 URL"); + return null; + } + try { + GetPresignedObjectUrlArgs presignedUrlArgs = GetPresignedObjectUrlArgs.builder().bucket(properties.getOss().getMinio().getBucketName()).object(fullFileName).method(Method.GET) + // 设置 URL 过期时间为 7 天, 最大 7 天, 别挣扎了 + .expiry(3600 * 24 * 7).build(); + String url = minioClient.getPresignedObjectUrl(presignedUrlArgs); + log.info("生成预签名 URL 成功,fileName: {}, url: {}", fullFileName, url); + return url; + } catch (Exception e) { + log.error("生成预签名 URL 失败,fileName: {}", fullFileName, e); + } + return null; + } + + /** + * 下载文件 + * + * @param fileName 文件名称 + * @param res HTTP 响应 + */ + public void download(String fileName, HttpServletResponse res) { + GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(properties.getOss().getMinio().getBucketName()).object(fileName).build(); + + try (GetObjectResponse response = minioClient.getObject(objectArgs); FastByteArrayOutputStream os = new FastByteArrayOutputStream()) { + + byte[] buf = new byte[1024]; + int len; + while ((len = response.read(buf)) != -1) { + os.write(buf, 0, len); + } + os.flush(); + + byte[] bytes = os.toByteArray(); + res.setCharacterEncoding("utf-8"); + res.addHeader("Content-Disposition", "attachment;fileName=" + fileName); + try (ServletOutputStream stream = res.getOutputStream()) { + stream.write(bytes); + stream.flush(); + } + } catch (Exception e) { + log.error("下载文件失败,fileName: {}, 错误信息", fileName, e); + } + } + + /** + * 文件下载:优化版 + * + * @param fileName 文件名称 + * @param res HTTP 响应 + */ + public void downloadOptimizedVersion(String fileName, HttpServletResponse res) { + GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(properties.getOss().getMinio().getBucketName()).object(fileName).build(); + + try (GetObjectResponse response = minioClient.getObject(objectArgs)) { + // 增大缓冲区 + byte[] buf = new byte[1024 * 4]; + int len; + + // 分块下载并输出到响应流 + try (ServletOutputStream stream = res.getOutputStream()) { + res.setCharacterEncoding("utf-8"); + res.addHeader("Content-Disposition", "attachment;fileName=" + fileName); + res.setHeader("Cache-Control", "no-store"); + res.setHeader("Pragma", "no-cache"); + res.setHeader("Expires", "0"); + + while ((len = response.read(buf)) != -1) { + stream.write(buf, 0, len); + // 确保实时写入 + stream.flush(); + } + log.info("文件下载成功,fileName: {}", fileName); + } + } catch (Exception e) { + log.error("下载文件失败,fileName: {}, 错误信息", fileName, e); + } + } + + /** + * 查看文件对象列表 + * + * @return List 文件对象列表 + */ + public List queryAllObjects() { + try { + Iterable> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(properties.getOss().getMinio().getBucketName()).build()); + List items = new ArrayList<>(); + for (Result result : results) { + items.add(result.get()); + } + return items; + } catch (Exception e) { + log.error("列出文件对象失败,错误信息", e); + } + return Collections.emptyList(); + } + + /** + * 删除文件 + * + * @param fileName 文件名 + * @return boolean 删除成功返回 true,否则返回 false + */ + public boolean removeBucketFile(String fileName) { + try { + minioClient.removeObject(RemoveObjectArgs.builder().bucket(properties.getOss().getMinio().getBucketName()).object(fileName).build()); + log.info("删除文件成功,fileName: {}", fileName); + return true; + } catch (Exception e) { + log.error("删除文件失败,fileName: {}, 错误信息", fileName, e); + } + return false; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/job/TestTask.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/job/TestTask.java new file mode 100644 index 00000000..ff6c9e61 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/job/TestTask.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.job; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class TestTask { + + public void run() { + log.info("run 执行成功"); + } + + public void run1(String str) { + log.info("run1 执行成功, 参数为: {}", str); + } + + public void run2() { + log.info("run2 执行成功"); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDataService.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDataService.java new file mode 100644 index 00000000..9ca6f3da --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDataService.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service; + +import cn.hutool.core.collection.CollUtil; +import cn.odboy.system.constant.SystemDataScopeEnum; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import cn.odboy.system.dal.dataobject.SystemUserTb; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 数据权限 Service + * + * @author odboy + */ +@Service +public class SystemDataService { + @Autowired + private SystemRoleService systemRoleService; + @Autowired + private SystemDeptService systemDeptService; + + /** + * 获取数据权限 + * + * @param user / + * @return / + */ + public List queryDeptIdListByArgs(SystemUserTb user) { + List deptIds = new ArrayList<>(); + // 查询用户角色 + List roleList = systemRoleService.queryRoleByUsersId(user.getId()); + // 获取对应的部门ID + for (SystemRoleTb role : roleList) { + SystemDataScopeEnum dataScopeEnum = SystemDataScopeEnum.find(role.getDataScope()); + switch (Objects.requireNonNull(dataScopeEnum)) { + case THIS_LEVEL: + deptIds.add(user.getDept().getId()); + break; + case CUSTOMIZE: + deptIds.addAll(queryCustomDataPermissionList(deptIds, role)); + break; + default: + return new ArrayList<>(); + } + } + return deptIds.stream().distinct().collect(Collectors.toList()); + } + + /** + * 获取自定义的数据权限 + * + * @param deptIds 部门ID + * @param role 角色 + * @return 数据权限ID + */ + private List queryCustomDataPermissionList(List deptIds, SystemRoleTb role) { + List deptList = systemDeptService.queryDeptByRoleId(role.getId()); + for (SystemDeptTb dept : deptList) { + deptIds.add(dept.getId()); + List deptChildren = systemDeptService.queryDeptByPid(dept.getId()); + if (CollUtil.isNotEmpty(deptChildren)) { + deptIds.addAll(systemDeptService.queryChildDeptIdListByDeptIds(deptChildren)); + } + } + return deptIds; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDeptService.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDeptService.java new file mode 100644 index 00000000..5443806d --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDeptService.java @@ -0,0 +1,469 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.CsPageResult; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.system.constant.SystemDataScopeEnum; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.model.SystemCreateDeptArgs; +import cn.odboy.system.dal.model.SystemProductLineTreeVo; +import cn.odboy.system.dal.model.SystemProductLineVo; +import cn.odboy.system.dal.model.SystemQueryDeptArgs; +import cn.odboy.system.dal.mysql.SystemDeptMapper; +import cn.odboy.system.dal.mysql.SystemRoleMapper; +import cn.odboy.system.dal.mysql.SystemUserMapper; +import cn.odboy.system.framework.permission.core.CsSecurityHelper; +import cn.odboy.util.CsClassUtil; +import cn.odboy.util.CsFileUtil; +import cn.odboy.util.CsStringUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +public class SystemDeptService { + @Autowired + private SystemDeptMapper systemDeptMapper; + @Autowired + private SystemUserMapper systemUserMapper; + @Autowired + private SystemRoleMapper systemRoleMapper; + + /** + * 创建 + * + * @param args / + */ + + @Transactional(rollbackFor = Exception.class) + public void saveDept(SystemCreateDeptArgs args) { + systemDeptMapper.insert(BeanUtil.copyProperties(args, SystemDeptTb.class)); + this.updateDeptSubCnt(args.getPid()); + } + + /** + * 编辑 + * + * @param args / + */ + + @Transactional(rollbackFor = Exception.class) + public void modifyDept(SystemDeptTb args) { + // 旧的部门 + Long oldPid = queryDeptById(args.getId()).getPid(); + Long newPid = args.getPid(); + if (args.getPid() != null && args.getId().equals(args.getPid())) { + throw new BadRequestException("上级不能为自己"); + } + SystemDeptTb dept = systemDeptMapper.selectById(args.getId()); + args.setId(dept.getId()); + systemDeptMapper.insertOrUpdate(args); + this.updateDeptSubCnt(oldPid); + this.updateDeptSubCnt(newPid); + } + + /** + * 删除 + * + * @param deptSet / + */ + + @Transactional(rollbackFor = Exception.class) + public void removeDeptByIds(Set deptSet) { + for (SystemDeptTb dept : deptSet) { + systemDeptMapper.deleteById(dept.getId()); + this.updateDeptSubCnt(dept.getPid()); + } + } + + /** + * 导出数据 + * + * @param deptList 待导出的数据 + * @param response / + * @throws IOException / + */ + public void exportDeptExcel(List deptList, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (SystemDeptTb dept : deptList) { + Map map = new LinkedHashMap<>(); + map.put("部门名称", dept.getName()); + map.put("部门状态", dept.getEnabled() ? "启用" : "停用"); + map.put("创建日期", dept.getCreateTime()); + list.add(map); + } + CsFileUtil.downloadExcel(list, response); + } + + /** + * 更新父节点中子节点数目 + * + * @param deptId / + */ + @Transactional(rollbackFor = Exception.class) + public void updateDeptSubCnt(Long deptId) { + if (deptId != null) { + int count = systemDeptMapper.countDeptByPid(deptId); + systemDeptMapper.updateDeptSubCountById(count, deptId); + } + } + + /** + * 筛选出 list 中没有父部门(即 pid 不在其他部门 id 列表中的部门)的部门列表 + */ + private List deduplication(List list) { + List deptList = new ArrayList<>(); + // 使用 Set 存储所有部门的 id + Set idSet = list.stream().map(SystemDeptTb::getId).collect(Collectors.toSet()); + // 遍历部门列表, 筛选出没有父部门的部门 + for (SystemDeptTb dept : list) { + if (!idSet.contains(dept.getPid())) { + deptList.add(dept); + } + } + return deptList; + } + + /** + * 查询所有数据 + * + * @param criteria 条件 + * @param isQuery / + * @return / + * @throws Exception / + */ + + public List queryAllDept(SystemQueryDeptArgs criteria, Boolean isQuery) throws Exception { + String dataScopeType = CsSecurityHelper.getDataScopeType(); + if (isQuery) { + if (dataScopeType.equals(SystemDataScopeEnum.ALL.getValue())) { + criteria.setPidIsNull(true); + } + List fields = CsClassUtil.getAllFields(criteria.getClass(), new ArrayList<>()); + List fieldNames = new ArrayList<>() {{ + add("pidIsNull"); + add("enabled"); + }}; + for (Field field : fields) { + // 设置对象的访问权限, 保证对private的属性的访问 + field.setAccessible(true); + Object val = field.get(criteria); + if (fieldNames.contains(field.getName())) { + continue; + } + if (ObjectUtil.isNotNull(val)) { + criteria.setPidIsNull(null); + break; + } + } + } + // 数据权限 + criteria.setIds(CsSecurityHelper.getCurrentUserDataScope()); + List list = systemDeptMapper.selectDeptByArgs(criteria); + // 如果为空, 就代表为自定义权限或者本级权限, 就需要去重, 不理解可以注释掉,看查询结果 + if (StrUtil.isBlank(dataScopeType)) { + return deduplication(list); + } + return list; + } + + /** + * 根据ID查询 + * + * @param id / + * @return / + */ + + public SystemDeptTb queryDeptById(Long id) { + return systemDeptMapper.selectById(id); + } + + /** + * 根据PID查询 + * + * @param pid / + * @return / + */ + + public List queryDeptByPid(long pid) { + return systemDeptMapper.selectDeptByPid(pid); + } + + /** + * 根据角色ID查询 + * + * @param id / + * @return / + */ + + public List queryDeptByRoleId(Long id) { + return systemDeptMapper.selectDeptByRoleId(id); + } + + /** + * 获取部门下所有关联的部门 + * + * @param deptTbList / + * @param depts / + * @return / + */ + + public Set queryRelationDeptSet(List deptTbList, Set depts) { + for (SystemDeptTb dept : deptTbList) { + depts.add(dept); + List deptList = systemDeptMapper.selectDeptByPid(dept.getId()); + if (CollUtil.isNotEmpty(deptList)) { + queryRelationDeptSet(deptList, depts); + } + } + return depts; + } + + /** + * 获取 + * + * @param deptList 、 + * @return 、 + */ + + public List queryChildDeptIdListByDeptIds(List deptList) { + List list = new ArrayList<>(); + for (SystemDeptTb systemDeptTb : deptList) { + if (systemDeptTb != null && systemDeptTb.getEnabled()) { + List deptList1 = systemDeptMapper.selectDeptByPid(systemDeptTb.getId()); + if (CollUtil.isNotEmpty(deptList1)) { + list.addAll(queryChildDeptIdListByDeptIds(deptList1)); + } + list.add(systemDeptTb.getId()); + } + } + return list; + } + + /** + * 根据ID获取同级与上级数据 + * + * @param dept / + * @param deptList / + * @return / + */ + + public List querySuperiorDeptListByPid(SystemDeptTb dept, List deptList) { + if (dept.getPid() == null) { + deptList.addAll(systemDeptMapper.selectDeptByPidIsNull()); + return deptList; + } + deptList.addAll(systemDeptMapper.selectDeptByPid(dept.getPid())); + return querySuperiorDeptListByPid(queryDeptById(dept.getPid()), deptList); + } + + /** + * 构建树形数据 + * + * @param deptList / + * @return / + */ + + public CsPageResult buildDeptTree(List deptList) { + Set trees = new LinkedHashSet<>(); + Set deptSet = new LinkedHashSet<>(); + List deptNames = deptList.stream().map(SystemDeptTb::getName).collect(Collectors.toList()); + boolean isChild; + for (SystemDeptTb dept : deptList) { + isChild = false; + if (dept.getPid() == null) { + trees.add(dept); + } + for (SystemDeptTb it : deptList) { + if (it.getPid() != null && dept.getId().equals(it.getPid())) { + isChild = true; + if (dept.getChildren() == null) { + dept.setChildren(new ArrayList<>()); + } + dept.getChildren().add(it); + } + } + if (isChild) { + deptSet.add(dept); + } else if (dept.getPid() != null && !deptNames.contains(queryDeptById(dept.getPid()).getName())) { + deptSet.add(dept); + } + } + if (CollectionUtil.isEmpty(trees)) { + trees = deptSet; + } + CsPageResult baseResult = new CsPageResult<>(); + baseResult.setContent(CollectionUtil.isEmpty(trees) ? new ArrayList<>(deptSet) : new ArrayList<>(trees)); + baseResult.setTotalElements(deptSet.size()); + return baseResult; + } + + /** + * 验证是否被角色或用户关联 + * + * @param deptSet / + */ + + public void verifyBindRelationByIds(Set deptSet) { + Set deptIds = deptSet.stream().map(SystemDeptTb::getId).collect(Collectors.toSet()); + if (systemUserMapper.countUserByDeptIds(deptIds) > 0) { + throw new BadRequestException("所选部门存在用户关联,请解除后再试!"); + } + if (systemRoleMapper.countRoleByDeptIds(deptIds) > 0) { + throw new BadRequestException("所选部门存在角色关联,请解除后再试!"); + } + } + + /** + * 遍历所有部门和子部门 + * + * @param ids / + */ + + public Set traverseDeptByIdWithPids(Set ids) { + Set depts = new HashSet<>(); + for (Long id : ids) { + // 根部门 + depts.add(queryDeptById(id)); + // 子部门 + List deptList = queryDeptByPid(id); + if (CollectionUtil.isNotEmpty(deptList)) { + depts = queryRelationDeptSet(deptList, depts); + } + } + return depts; + } + + public List queryDeptSelectDataSource() { + List depts = findEnabledDepts(); + return buildDeptSelectOptions(depts); + } + + private List buildDeptSelectOptions(List depts) { + // 获取所有部门并按父子关系组织 + Map deptMap = + depts.stream().collect(Collectors.toMap(SystemDeptTb::getId, Function.identity())); + List options = new ArrayList<>(); + for (SystemDeptTb dept : depts) { + // 构建部门ID路径 + List pathIds = new ArrayList<>(); + buildDeptIdPath(dept, deptMap, pathIds); + + // 反转路径,从顶级部门到当前部门 + Collections.reverse(pathIds); + + SystemProductLineVo dto = new SystemProductLineVo(); + dto.setValue(dept.getId()); + dto.setIdPath(pathIds.stream().map(String::valueOf).collect(Collectors.joining("-"))); + // 保留名称路径用于显示 + dto.setLabel(buildNamePath(dept, deptMap)); + options.add(dto); + } + + // 按部门名称路径排序 + options.sort(Comparator.comparing(SystemProductLineVo::getLabel)); + return options; + } + + private void buildDeptIdPath(SystemDeptTb dept, Map deptMap, List pathIds) { + pathIds.add(dept.getId()); + if (dept.getPid() != null && dept.getPid() != 0 && deptMap.containsKey(dept.getPid())) { + buildDeptIdPath(deptMap.get(dept.getPid()), deptMap, pathIds); + } + } + + private String buildNamePath(SystemDeptTb dept, Map deptMap) { + List pathNames = new ArrayList<>(); + buildDeptNamePath(dept, deptMap, pathNames); + Collections.reverse(pathNames); + return String.join(" / ", pathNames); + } + + private void buildDeptNamePath(SystemDeptTb dept, Map deptMap, List pathNames) { + pathNames.add(dept.getName()); + if (dept.getPid() != null && dept.getPid() != 0 && deptMap.containsKey(dept.getPid())) { + buildDeptNamePath(deptMap.get(dept.getPid()), deptMap, pathNames); + } + } + + private List findEnabledDepts() { + return systemDeptMapper.selectList(new LambdaQueryWrapper().eq(SystemDeptTb::getEnabled, 1)); + } + + public List queryDeptSelectProDataSource() { + List depts = findEnabledDepts(); + // 获取所有部门并按父子关系组织 + Map deptMap = + depts.stream().collect(Collectors.toMap(SystemDeptTb::getId, Function.identity())); + List options = new ArrayList<>(); + // 构建树形结构 + for (SystemDeptTb dept : depts) { + // 只处理顶级部门(pid为null或0的部门) + if (dept.getPid() == null || dept.getPid() == 0) { + SystemProductLineTreeVo vo = buildDeptTreePro(dept, deptMap); + options.add(vo); + } + } + // 按排序字段排序 + options.sort(Comparator.comparingInt(o -> { + Long id = Long.valueOf(o.getValue()); + return deptMap.get(id) != null ? deptMap.get(id).getDeptSort() : 0; + })); + return options; + } + + /** + * 递归构建部门树 + * + * @param dept 当前部门 + * @param deptMap 部门映射 + * @return SystemProductLineTreeVo 树节点 + */ + private SystemProductLineTreeVo buildDeptTreePro(SystemDeptTb dept, Map deptMap) { + SystemProductLineTreeVo vo = new SystemProductLineTreeVo(); + vo.setValue(String.valueOf(dept.getId())); + vo.setLabel(dept.getName()); + // 查找子部门 + List children = new ArrayList<>(); + for (SystemDeptTb childDept : deptMap.values()) { + if (dept.getId().equals(childDept.getPid())) { + children.add(buildDeptTreePro(childDept, deptMap)); + } + } + // 子部门按排序字段排序 + children.sort(Comparator.comparingInt(o -> { + Long id = Long.valueOf(o.getValue()); + return deptMap.get(id) != null ? deptMap.get(id).getDeptSort() : 0; + })); + vo.setChildren(children); + return vo; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictDetailService.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictDetailService.java new file mode 100644 index 00000000..301cb0ad --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictDetailService.java @@ -0,0 +1,98 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service; + +import cn.hutool.core.bean.BeanUtil; +import cn.odboy.base.CsPageResult; +import cn.odboy.system.dal.dataobject.SystemDictDetailTb; +import cn.odboy.system.dal.model.SystemCreateDictDetailArgs; +import cn.odboy.system.dal.model.SystemQueryDictDetailArgs; +import cn.odboy.system.dal.mysql.SystemDictDetailMapper; +import cn.odboy.util.CsPageUtil; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +public class SystemDictDetailService { + @Autowired + private SystemDictDetailMapper systemDictDetailMapper; + + /** + * 创建 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void saveDictDetail(SystemCreateDictDetailArgs args) { + SystemDictDetailTb dictDetail = BeanUtil.copyProperties(args, SystemDictDetailTb.class); + dictDetail.setDictId(args.getDict().getId()); + systemDictDetailMapper.insert(dictDetail); + } + + /** + * 编辑 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void modifyDictDetailById(SystemDictDetailTb args) { + SystemDictDetailTb dictDetail = systemDictDetailMapper.selectById(args.getId()); + args.setId(dictDetail.getId()); + systemDictDetailMapper.insertOrUpdate(args); + } + + /** + * 删除 + * + * @param id / + */ + + @Transactional(rollbackFor = Exception.class) + public void removeDictDetailById(Long id) { + systemDictDetailMapper.deleteById(id); + } + + /** + * 分页查询 + * + * @param args 条件 + * @param page 分页参数 + * @return / + */ + + public CsPageResult queryDictDetailByArgs(SystemQueryDictDetailArgs args, Page page) { + return CsPageUtil.toPage(systemDictDetailMapper.selectDictDetailByArgs(args, page)); + } + + /** + * 根据字典名称获取字典详情 + * + * @param name 字典名称 + * @return / + */ + + public List queryDictDetailByName(String name) { + SystemQueryDictDetailArgs args = new SystemQueryDictDetailArgs(); + args.setDictName(name); + return systemDictDetailMapper.selectDictDetailByArgs(args); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictService.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictService.java new file mode 100644 index 00000000..6fb39e37 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictService.java @@ -0,0 +1,149 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.odboy.base.CsPageResult; +import cn.odboy.system.dal.dataobject.SystemDictDetailTb; +import cn.odboy.system.dal.dataobject.SystemDictTb; +import cn.odboy.system.dal.model.SystemCreateDictArgs; +import cn.odboy.system.dal.model.SystemQueryDictArgs; +import cn.odboy.system.dal.model.SystemQueryDictDetailArgs; +import cn.odboy.system.dal.mysql.SystemDictDetailMapper; +import cn.odboy.system.dal.mysql.SystemDictMapper; +import cn.odboy.util.CsFileUtil; +import cn.odboy.util.CsPageUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + +@Service +public class SystemDictService { + @Autowired + private SystemDictMapper systemDictMapper; + @Autowired + private SystemDictDetailMapper systemDictDetailMapper; + + /** + * 创建 + * + * @param args / + */ + + @Transactional(rollbackFor = Exception.class) + public void saveDict(SystemCreateDictArgs args) { + systemDictMapper.insert(BeanUtil.copyProperties(args, SystemDictTb.class)); + } + + /** + * 编辑 + * + * @param args / + */ + + @Transactional(rollbackFor = Exception.class) + public void modifyDictById(SystemDictTb args) { + SystemDictTb dict = systemDictMapper.selectById(args.getId()); + dict.setName(args.getName()); + dict.setDescription(args.getDescription()); + systemDictMapper.insertOrUpdate(dict); + } + + /** + * 删除 + * + * @param ids / + */ + + @Transactional(rollbackFor = Exception.class) + public void removeDictByIds(Set ids) { + // 删除字典 + systemDictMapper.deleteByIds(ids); + // 删除字典详情 + systemDictDetailMapper.deleteDictDetailByDictIds(ids); + } + + /** + * 导出数据 + * + * @param dicts 待导出的数据 + * @param response / + * @throws IOException / + */ + + public void exportDictExcel(List dicts, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (SystemDictTb dict : dicts) { + SystemQueryDictDetailArgs args = new SystemQueryDictDetailArgs(); + args.setDictName(dict.getName()); + List dictDetails = systemDictDetailMapper.selectDictDetailByArgs(args); + if (CollectionUtil.isNotEmpty(dictDetails)) { + for (SystemDictDetailTb dictDetail : dictDetails) { + Map map = new LinkedHashMap<>(); + map.put("字典名称", dict.getName()); + map.put("字典描述", dict.getDescription()); + map.put("字典标签", dictDetail.getLabel()); + map.put("字典值", dictDetail.getValue()); + map.put("创建日期", dictDetail.getCreateTime()); + list.add(map); + } + } else { + Map map = new LinkedHashMap<>(); + map.put("字典名称", dict.getName()); + map.put("字典描述", dict.getDescription()); + map.put("字典标签", null); + map.put("字典值", null); + map.put("创建日期", dict.getCreateTime()); + list.add(map); + } + } + CsFileUtil.downloadExcel(list, response); + } + + /** + * 分页查询 + * + * @param args 条件 + * @param page 分页参数 + * @return / + */ + + public CsPageResult queryDictByArgs(SystemQueryDictArgs args, Page page) { + return CsPageUtil.toPage(systemDictMapper.selectDictByArgs(args, page)); + } + + /** + * 查询全部数据 + * + * @param args / + * @return / + */ + + public List queryDictByArgs(SystemQueryDictArgs args) { + return systemDictMapper.selectDictByArgs(args); + } + + public SystemDictTb getById(int id) { + return systemDictMapper.selectById(id); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemEmailService.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemEmailService.java new file mode 100644 index 00000000..472a6351 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemEmailService.java @@ -0,0 +1,164 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.extra.mail.Mail; +import cn.hutool.extra.mail.MailAccount; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.redis.CsRedisHelper; +import cn.odboy.system.constant.SystemCaptchaBizEnum; +import cn.odboy.system.dal.dataobject.SystemEmailConfigTb; +import cn.odboy.system.dal.model.SystemSendEmailArgs; +import cn.odboy.system.dal.mysql.SystemEmailConfigMapper; +import cn.odboy.util.CsDesEncryptUtil; +import cn.odboy.util.CsResourceTemplateUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; + +@Slf4j +@Service +public class SystemEmailService { + @Autowired + private SystemEmailConfigMapper systemEmailConfigMapper; + @Autowired + private CsRedisHelper redisHelper; + @Autowired + private AppProperties properties; + @Value("${spring.application.title}") + private String applicationTitle; + + /** + * 更新邮件配置 + * + * @param emailConfig 邮箱配置 + */ + + @Transactional(rollbackFor = Exception.class) + public void modifyEmailConfig(SystemEmailConfigTb emailConfig) throws Exception { + SystemEmailConfigTb systemEmailConfigTb = getLastEmailConfig(); + if (!emailConfig.getPassword().equals(systemEmailConfigTb.getPassword())) { + // 对称加密 + systemEmailConfigTb.setPassword(CsDesEncryptUtil.desEncrypt(emailConfig.getPassword())); + } + systemEmailConfigTb.setId(1L); + systemEmailConfigMapper.insertOrUpdate(systemEmailConfigTb); + } + + /** + * 发送邮件 + * + * @param sendEmailRequest 邮件发送的内容 + */ + + public void sendEmail(SystemSendEmailArgs sendEmailRequest) { + SystemEmailConfigTb systemEmailConfigTb = getLastEmailConfig(); + if (systemEmailConfigTb.getId() == null) { + throw new BadRequestException("请先配置, 再操作"); + } + // 封装 + MailAccount account = new MailAccount(); + // 设置用户 + String user = systemEmailConfigTb.getFromUser().split("@")[0]; + account.setUser(user); + account.setHost(systemEmailConfigTb.getHost()); + account.setPort(Integer.parseInt(systemEmailConfigTb.getPort())); + account.setAuth(true); + try { + // 对称解密 + account.setPass(CsDesEncryptUtil.desDecrypt(systemEmailConfigTb.getPassword())); + } catch (Exception e) { + throw new BadRequestException(e.getMessage()); + } + account.setFrom(systemEmailConfigTb.getUser() + "<" + systemEmailConfigTb.getFromUser() + ">"); + // ssl方式发送 + account.setSslEnable(true); + // 使用STARTTLS安全连接 + account.setStarttlsEnable(true); + // 解决jdk8之后默认禁用部分tls协议, 导致邮件发送失败的问题 + account.setSslProtocols("TLSv1 TLSv1.1 TLSv1.2"); + String content = sendEmailRequest.getContent(); + // 发送 + try { + int size = sendEmailRequest.getTos().size(); + Mail.create(account) + .setTos(sendEmailRequest.getTos().toArray(new String[size])) + .setTitle(sendEmailRequest.getSubject()) + .setContent(content) + .setHtml(true) + // 关闭session + .setUseGlobalSession(false) + .send(); + } catch (Exception e) { + log.error("邮件发送失败", e); + throw new BadRequestException("邮件发送失败"); + } + } + + /** + * 校验邮箱验证码 + */ + + public void checkEmailCaptcha(SystemCaptchaBizEnum biEnum, String email, String code) { + String redisKey = biEnum.getRedisKey() + email; + String value = redisHelper.get(redisKey, String.class); + if (value == null || !value.equals(code)) { + throw new BadRequestException("无效验证码"); + } else { + redisHelper.del(redisKey); + } + } + + /** + * 发送给邮箱验证码 + */ + + public void sendCaptcha(SystemCaptchaBizEnum biEnum, String email) { + if (biEnum == null) { + throw new BadRequestException("biEnum必填"); + } + String content; + String redisKey = biEnum.getRedisKey() + email; + String oldCode = redisHelper.get(redisKey, String.class); + if (oldCode == null) { + String code = RandomUtil.randomNumbers(6); + // 存入缓存 + if (!redisHelper.set(redisKey, code, properties.getCaptcha().getExpireTime())) { + throw new BadRequestException("服务异常, 请联系网站负责人"); + } + // 存在就再次发送原来的验证码 + content = CsResourceTemplateUtil.render(null, biEnum.getTemplateName(), Dict.create().set("code", code)); + } else { + content = CsResourceTemplateUtil.render(null, biEnum.getTemplateName(), Dict.create().set("code", oldCode)); + } + SystemSendEmailArgs sendEmailRequest = + new SystemSendEmailArgs(Collections.singletonList(email), applicationTitle, content); + sendEmail(sendEmailRequest); + } + + public SystemEmailConfigTb getLastEmailConfig() { + SystemEmailConfigTb systemEmailConfigTb = systemEmailConfigMapper.selectById(1L); + return systemEmailConfigTb == null ? new SystemEmailConfigTb() : systemEmailConfigTb; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemJobService.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemJobService.java new file mode 100644 index 00000000..eac526f7 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemJobService.java @@ -0,0 +1,142 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service; + +import cn.hutool.core.bean.BeanUtil; +import cn.odboy.base.CsPageResult; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.system.dal.dataobject.SystemJobTb; +import cn.odboy.system.dal.model.SystemCreateJobArgs; +import cn.odboy.system.dal.model.SystemQueryJobArgs; +import cn.odboy.system.dal.mysql.SystemJobMapper; +import cn.odboy.system.dal.mysql.SystemUserMapper; +import cn.odboy.util.CsFileUtil; +import cn.odboy.util.CsPageUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + +@Service +public class SystemJobService { + @Autowired + private SystemJobMapper systemJobMapper; + @Autowired + private SystemUserMapper systemUserMapper; + + /** + * 创建 + * + * @param args / + */ + + @Transactional(rollbackFor = Exception.class) + public void saveJob(SystemCreateJobArgs args) { + SystemJobTb job = systemJobMapper.getJobByName(args.getName()); + if (job != null) { + throw new BadRequestException("职位名称已存在"); + } + systemJobMapper.insert(BeanUtil.copyProperties(args, SystemJobTb.class)); + } + + /** + * 编辑 + * + * @param args / + */ + + @Transactional(rollbackFor = Exception.class) + public void modifyJobById(SystemJobTb args) { + SystemJobTb job = systemJobMapper.selectById(args.getId()); + SystemJobTb old = systemJobMapper.getJobByName(args.getName()); + if (old != null && !old.getId().equals(args.getId())) { + throw new BadRequestException("职位名称已存在"); + } + args.setId(job.getId()); + systemJobMapper.insertOrUpdate(args); + } + + /** + * 删除 + * + * @param ids / + */ + + @Transactional(rollbackFor = Exception.class) + public void removeJobByIds(Set ids) { + systemJobMapper.deleteByIds(ids); + } + + /** + * 导出数据 + * + * @param jobs 待导出的数据 + * @param response / + * @throws IOException + */ + + public void exportJobExcel(List jobs, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (SystemJobTb job : jobs) { + Map map = new LinkedHashMap<>(); + map.put("岗位名称", job.getName()); + map.put("岗位状态", job.getEnabled() ? "启用" : "停用"); + map.put("创建日期", job.getCreateTime()); + list.add(map); + } + CsFileUtil.downloadExcel(list, response); + } + + /** + * 分页查询 + * + * @param args 条件 + * @param page 分页参数 + * @return + */ + + public CsPageResult queryJobByArgs(SystemQueryJobArgs args, Page page) { + return CsPageUtil.toPage(systemJobMapper.selectJobByArgs(args, page)); + } + + /** + * 查询全部数据 + * + * @param args / + * @return / + */ + + public List queryJobByArgs(SystemQueryJobArgs args) { + return systemJobMapper.selectJobByArgs(args, CsPageUtil.getCount(systemJobMapper)).getRecords(); + } + + /** + * 验证是否被用户关联 + * + * @param ids / + */ + + public void verifyBindRelationByIds(Set ids) { + if (systemUserMapper.countUserByJobIds(ids) > 0) { + throw new BadRequestException("所选的岗位中存在用户关联, 请解除关联再试!"); + } + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemLocalStorageService.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemLocalStorageService.java new file mode 100644 index 00000000..909e62fd --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemLocalStorageService.java @@ -0,0 +1,166 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.CsPageResult; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.server.core.CsFileLocalUploadHelper; +import cn.odboy.system.dal.dataobject.SystemLocalStorageTb; +import cn.odboy.system.dal.model.SystemQueryStorageArgs; +import cn.odboy.system.dal.mysql.SystemLocalStorageMapper; +import cn.odboy.util.CsFileUtil; +import cn.odboy.util.CsPageUtil; +import cn.odboy.util.CsStringUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.util.*; + +@Service +public class SystemLocalStorageService { + @Autowired + private SystemLocalStorageMapper systemLocalStorageMapper; + @Autowired + private CsFileLocalUploadHelper fileUploadPathHelper; + @Autowired + private AppProperties properties; + + /** + * 上传 + * + * @param name 文件名称 + * @param multipartFile 文件 + * @return / + */ + @Transactional(rollbackFor = Exception.class) + public SystemLocalStorageTb uploadFile(String name, MultipartFile multipartFile) { + long size = multipartFile.getSize(); + CsFileUtil.checkSize(properties.getOss().getMaxSize(), size); + String suffix = CsFileUtil.getSuffix(multipartFile.getOriginalFilename()); + String type = CsFileUtil.getFileType(suffix); + String uploadDateStr = DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT); + File file = CsFileUtil.upload(multipartFile, fileUploadPathHelper.getPath() + uploadDateStr + File.separator); + if (file == null) { + throw new BadRequestException("上传失败"); + } + try { + String formatSize = CsFileUtil.getSize(size); + String prefixName = StrUtil.isBlank(name) ? CsFileUtil.getPrefix(multipartFile.getOriginalFilename()) : name; + SystemLocalStorageTb localStorage = new SystemLocalStorageTb(); + localStorage.setRealName(file.getName()); + localStorage.setName(prefixName); + localStorage.setSuffix(suffix); + localStorage.setPath(file.getPath()); + localStorage.setType(type); + localStorage.setSize(formatSize); + localStorage.setDateGroup(uploadDateStr); + systemLocalStorageMapper.insert(localStorage); + return localStorage; + } catch (Exception e) { + CsFileUtil.del(file); + throw e; + } + } + + /** + * 编辑 + * + * @param args 文件信息 + */ + @Transactional(rollbackFor = Exception.class) + public void modifyLocalStorageById(SystemLocalStorageTb args) { + SystemLocalStorageTb localStorage = systemLocalStorageMapper.selectById(args.getId()); + localStorage.copy(args); + systemLocalStorageMapper.insertOrUpdate(localStorage); + } + + /** + * 多选删除 + * + * @param ids / + */ + @Transactional(rollbackFor = Exception.class) + public void removeFileByIds(Long[] ids) { + for (Long id : ids) { + SystemLocalStorageTb storage = systemLocalStorageMapper.selectById(id); + try { + CsFileUtil.del(storage.getPath()); + systemLocalStorageMapper.deleteById(storage); + } catch (IORuntimeException e) { + throw new BadRequestException("删除文件 " + storage.getName() + " 失败"); + } + } + } + + /** + * 导出数据 + * + * @param localStorages 待导出的数据 + * @param response / + * @throws IOException / + */ + public void exportLocalStorageExcel(List localStorages, HttpServletResponse response) + throws IOException { + List> list = new ArrayList<>(); + for (SystemLocalStorageTb localStorage : localStorages) { + Map map = new LinkedHashMap<>(); + map.put("文件名", localStorage.getRealName()); + map.put("备注名", localStorage.getName()); + map.put("文件类型", localStorage.getType()); + map.put("文件大小", localStorage.getSize()); + map.put("创建者", localStorage.getCreateBy()); + map.put("创建日期", localStorage.getCreateTime()); + list.add(map); + } + CsFileUtil.downloadExcel(list, response); + } + + /** + * 分页查询 + * + * @param criteria 条件 + * @param page 分页参数 + * @return / + */ + public CsPageResult queryLocalStorage( + SystemQueryStorageArgs criteria, + Page page) { + return CsPageUtil.toPage(systemLocalStorageMapper.selectLocalStorageByArgs(criteria, page)); + } + + /** + * 查询全部数据 + * + * @param criteria 条件 + * @return / + */ + public List queryLocalStorage(SystemQueryStorageArgs criteria) { + return systemLocalStorageMapper.selectLocalStorageByArgs(criteria, CsPageUtil.getCount(systemLocalStorageMapper)) + .getRecords(); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMenuService.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMenuService.java new file mode 100644 index 00000000..ac11bad4 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMenuService.java @@ -0,0 +1,405 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.framework.context.CsSpringBeanHolder; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.system.constant.TransferProtocolConst; +import cn.odboy.system.dal.dataobject.SystemMenuTb; +import cn.odboy.system.dal.dataobject.SystemRoleMenuTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import cn.odboy.system.dal.model.SystemMenuMetaVo; +import cn.odboy.system.dal.model.SystemMenuVo; +import cn.odboy.system.dal.model.SystemQueryMenuArgs; +import cn.odboy.system.dal.mysql.SystemMenuMapper; +import cn.odboy.system.dal.mysql.SystemRoleMenuMapper; +import cn.odboy.util.CsClassUtil; +import cn.odboy.util.CsFileUtil; +import cn.odboy.util.CsStringUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class SystemMenuService { + @Autowired + private SystemMenuMapper systemMenuMapper; + @Autowired + private SystemRoleMenuMapper systemRoleMenuMapper; + @Autowired + private SystemRoleService systemRoleService; + + private static final String YES_STR = "是"; + private static final String NO_STR = "否"; + + /** + * 创建 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void saveMenu(SystemMenuTb args) { + if (systemMenuMapper.getMenuByTitle(args.getTitle()) != null) { + throw new BadRequestException("菜单标题已存在"); + } + if (StrUtil.isNotBlank(args.getComponentName())) { + if (systemMenuMapper.getMenuByComponentName(args.getComponentName()) != null) { + throw new BadRequestException("菜单组件名称已存在"); + } + } + if (Long.valueOf(0L).equals(args.getPid())) { + args.setPid(null); + } + if (args.getIFrame()) { + if (!(args.getPath().toLowerCase().startsWith(TransferProtocolConst.PREFIX_HTTP) || args.getPath().toLowerCase().startsWith(TransferProtocolConst.PREFIX_HTTPS))) { + throw new BadRequestException(TransferProtocolConst.PREFIX_HTTPS_BAD_REQUEST); + } + } + systemMenuMapper.insert(args); + // 计算子节点数目 + args.setSubCount(0); + // 更新父节点菜单数目 + this.updateMenuSubCnt(args.getPid()); + } + + /** + * 编辑 + * + * @param args / + */ + + @Transactional(rollbackFor = Exception.class) + public void modifyMenuById(SystemMenuTb args) { + if (args.getId().equals(args.getPid())) { + throw new BadRequestException("上级不能为自己"); + } + SystemMenuTb menu = systemMenuMapper.selectById(args.getId()); + if (args.getIFrame()) { + if (!(args.getPath().toLowerCase().startsWith(TransferProtocolConst.PREFIX_HTTP) || args.getPath().toLowerCase().startsWith(TransferProtocolConst.PREFIX_HTTPS))) { + throw new BadRequestException(TransferProtocolConst.PREFIX_HTTPS_BAD_REQUEST); + } + } + SystemMenuTb menu1 = systemMenuMapper.getMenuByTitle(args.getTitle()); + + if (menu1 != null && !menu1.getId().equals(menu.getId())) { + throw new BadRequestException("菜单标题已存在"); + } + + if (args.getPid().equals(0L)) { + args.setPid(null); + } + + // 记录的父节点ID + Long oldPid = menu.getPid(); + Long newPid = args.getPid(); + + if (StrUtil.isNotBlank(args.getComponentName())) { + menu1 = systemMenuMapper.getMenuByComponentName(args.getComponentName()); + if (menu1 != null && !menu1.getId().equals(menu.getId())) { + throw new BadRequestException("菜单组件名称已存在"); + } + } + menu.setTitle(args.getTitle()); + menu.setComponent(args.getComponent()); + menu.setPath(args.getPath()); + menu.setIcon(args.getIcon()); + menu.setIFrame(args.getIFrame()); + menu.setPid(args.getPid()); + menu.setMenuSort(args.getMenuSort()); + menu.setCache(args.getCache()); + menu.setHidden(args.getHidden()); + menu.setComponentName(args.getComponentName()); + menu.setPermission(args.getPermission()); + menu.setType(args.getType()); + // auto fill + menu.setUpdateBy(null); + menu.setUpdateTime(null); + systemMenuMapper.insertOrUpdate(menu); + // 计算父级菜单节点数目 + this.updateMenuSubCnt(oldPid); + this.updateMenuSubCnt(newPid); + } + + /** + * 删除 + */ + @Transactional(rollbackFor = Exception.class) + public void removeMenuByIds(Set ids) { + Set menuSet = new HashSet<>(); + for (Long id : ids) { + List menuList = this.queryMenuByPid(id); + menuSet.add(systemMenuMapper.selectById(id)); + menuSet = this.queryChildMenu(menuList, menuSet); + } + List menuIds = menuSet.stream().map(SystemMenuTb::getId).filter(Objects::nonNull).distinct().collect(Collectors.toList()); + if (CollUtil.isNotEmpty(menuIds)) { + CsSpringBeanHolder.getBean(SystemMenuService.class).removeRoleMenuByMenuIds(menuIds); + systemMenuMapper.deleteByIds(menuIds); + } + for (SystemMenuTb menu : menuSet) { + this.updateMenuSubCnt(menu.getPid()); + } + } + + @Transactional(rollbackFor = Exception.class) + public void removeRoleMenuByMenuIds(List menuIds) { + systemRoleMenuMapper.delete(new LambdaQueryWrapper().in(SystemRoleMenuTb::getMenuId, menuIds)); + } + + /** + * 导出 + * + * @param menus 待导出的数据 + * @param response / + * @throws IOException / + */ + + public void exportMenuExcel(List menus, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (SystemMenuTb menu : menus) { + Map map = new LinkedHashMap<>(); + map.put("菜单标题", menu.getTitle()); + map.put("菜单类型", menu.getType() == null ? "目录" : menu.getType() == 1 ? "菜单" : "按钮"); + map.put("权限标识", menu.getPermission()); + map.put("外链菜单", menu.getIFrame() ? YES_STR : NO_STR); + map.put("菜单可见", menu.getHidden() ? NO_STR : YES_STR); + map.put("是否缓存", menu.getCache() ? YES_STR : NO_STR); + map.put("创建日期", menu.getCreateTime()); + list.add(map); + } + CsFileUtil.downloadExcel(list, response); + } + + @Transactional(rollbackFor = Exception.class) + public void updateMenuSubCnt(Long menuId) { + if (menuId != null) { + int count = systemMenuMapper.countMenuByPid(menuId); + systemMenuMapper.updateMenuSubCntByMenuId(count, menuId); + } + } + + /** + * 查询全部数据 + * + * @param criteria 条件 + * @param isQuery / + * @return / + * @throws Exception / + */ + + public List queryAllMenu(SystemQueryMenuArgs criteria, Boolean isQuery) throws Exception { + if (Boolean.TRUE.equals(isQuery)) { + criteria.setPidIsNull(true); + List fields = CsClassUtil.getAllFields(criteria.getClass(), new ArrayList<>()); + for (Field field : fields) { + //设置对象的访问权限, 保证对private的属性的访问 + field.setAccessible(true); + Object val = field.get(criteria); + if ("pidIsNull".equals(field.getName())) { + continue; + } + // 如果有查询条件, 则不指定pidIsNull + if (ObjectUtil.isNotNull(val)) { + criteria.setPidIsNull(null); + break; + } + } + } + return systemMenuMapper.selectMenuByArgs(criteria); + } + + /** + * 根据当前用户获取菜单 + * + * @param currentUserId / + * @return / + */ + + public List queryMenuListByUserId(Long currentUserId) { + List roles = systemRoleService.queryRoleByUsersId(currentUserId); + Set roleIds = roles.stream().map(SystemRoleTb::getId).collect(Collectors.toSet()); + return new ArrayList<>(systemMenuMapper.selectMenuByRoleIdsAndType(roleIds, 2)); + } + + /** + * 获取所有子节点, 包含自身ID + * + * @param menuList / + * @param menuSet / + * @return / + */ + + public Set queryChildMenu(List menuList, Set menuSet) { + for (SystemMenuTb menu : menuList) { + menuSet.add(menu); + List menus = systemMenuMapper.selectMenuByPidOrderByMenuSort(menu.getId()); + if (CollUtil.isNotEmpty(menus)) { + queryChildMenu(menus, menuSet); + } + } + return menuSet; + } + + /** + * 懒加载菜单数据 + * + * @param pid / + * @return / + */ + + public List queryMenuByPid(Long pid) { + List menus; + if (pid != null && !pid.equals(0L)) { + menus = systemMenuMapper.selectMenuByPidOrderByMenuSort(pid); + } else { + menus = systemMenuMapper.selectMenuByPidIsNullOrderByMenuSort(); + } + return menus; + } + + /** + * 根据ID获取同级与上级数据 + * + * @param menu / + * @param menus / + * @return / + */ + + public List querySuperiorMenuList(SystemMenuTb menu, List menus) { + if (menu.getPid() == null) { + menus.addAll(systemMenuMapper.selectMenuByPidIsNullOrderByMenuSort()); + return menus; + } + menus.addAll(systemMenuMapper.selectMenuByPidOrderByMenuSort(menu.getPid())); + return querySuperiorMenuList(systemMenuMapper.selectById(menu.getPid()), menus); + } + + /** + * 构建菜单树 + * + * @param menus 原始数据 + * @return / + */ + + public List buildMenuTree(List menus) { + List trees = new ArrayList<>(); + Set ids = new HashSet<>(); + for (SystemMenuTb menu : menus) { + if (menu.getPid() == null) { + trees.add(menu); + } + for (SystemMenuTb it : menus) { + if (menu.getId().equals(it.getPid())) { + if (menu.getChildren() == null) { + menu.setChildren(new ArrayList<>()); + } + menu.getChildren().add(it); + ids.add(it.getId()); + } + } + } + if (CollUtil.isNotEmpty(trees)) { + trees = menus.stream().filter(s -> !ids.contains(s.getId())).collect(Collectors.toList()); + } + return trees; + } + + /** + * 构建菜单树 + * + * @param menus / + * @return / + */ + + public List buildMenuVo(List menus) { + List list = new LinkedList<>(); + menus.forEach(menu -> { + if (menu != null) { + List menuList = menu.getChildren(); + SystemMenuVo menuVo = new SystemMenuVo(); + menuVo.setName(ObjectUtil.isNotEmpty(menu.getComponentName()) ? menu.getComponentName() : menu.getTitle()); + // 一级目录需要加斜杠, 不然会报警告 + menuVo.setPath(menu.getPid() == null ? "/" + menu.getPath() : menu.getPath()); + menuVo.setHidden(menu.getHidden()); + // 如果不是外链 + if (!menu.getIFrame()) { + if (menu.getPid() == null) { + menuVo.setComponent(StrUtil.isEmpty(menu.getComponent()) ? "Layout" : menu.getComponent()); + // 如果不是一级菜单,并且菜单类型为目录,则代表是多级菜单 + } else if (menu.getType() == 0) { + menuVo.setComponent(StrUtil.isEmpty(menu.getComponent()) ? "ParentView" : menu.getComponent()); + } else if (StrUtil.isNotBlank(menu.getComponent())) { + menuVo.setComponent(menu.getComponent()); + } + } + menuVo.setMeta(new SystemMenuMetaVo(menu.getTitle(), menu.getIcon(), !menu.getCache())); + if (CollectionUtil.isNotEmpty(menuList)) { + menuVo.setAlwaysShow(true); + menuVo.setRedirect("noredirect"); + menuVo.setChildren(buildMenuVo(menuList)); + // 处理是一级菜单并且没有子菜单的情况 + } else if (menu.getPid() == null) { + SystemMenuVo menuVo1 = getMenuVo(menu, menuVo); + menuVo.setName(null); + menuVo.setMeta(null); + menuVo.setComponent("Layout"); + List list1 = new ArrayList<>(); + list1.add(menuVo1); + menuVo.setChildren(list1); + } + list.add(menuVo); + } + }); + return list; + } + + /** + * 获取 MenuResponse + * + * @param menu / + * @param menuVo / + * @return / + */ + private SystemMenuVo getMenuVo(SystemMenuTb menu, SystemMenuVo menuVo) { + SystemMenuVo menuVo1 = new SystemMenuVo(); + menuVo1.setMeta(menuVo.getMeta()); + // 非外链 + if (!menu.getIFrame()) { + menuVo1.setPath("index"); + menuVo1.setName(menuVo.getName()); + menuVo1.setComponent(menuVo.getComponent()); + } else { + menuVo1.setPath(menu.getPath()); + } + return menuVo1; + } + + public List queryMenuByIds(List ids) { + return systemMenuMapper.selectByIds(ids); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMonitorService.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMonitorService.java new file mode 100644 index 00000000..b45e3d33 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMonitorService.java @@ -0,0 +1,209 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service; + +import cn.hutool.core.date.BetweenFormatter.Level; +import cn.hutool.core.date.DateUtil; +import cn.odboy.constant.SystemConst; +import cn.odboy.util.CsFileUtil; +import cn.odboy.util.CsIPUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.GlobalMemory; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.hardware.VirtualMemory; +import oshi.software.os.FileSystem; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.FormatUtil; +import oshi.util.Util; + +import java.lang.management.ManagementFactory; +import java.text.DecimalFormat; +import java.util.*; + +@Slf4j +@Service +public class SystemMonitorService { + private final DecimalFormat df = new DecimalFormat("0.00"); + + /** + * 查询服务器监控信息 + * + * @return Map + */ + public Map queryServerMonitorInfo() { + Map resultMap = new LinkedHashMap<>(8); + try { + SystemInfo si = new SystemInfo(); + OperatingSystem os = si.getOperatingSystem(); + HardwareAbstractionLayer hal = si.getHardware(); + // 系统信息 + resultMap.put("sys", getSystemInfo(os)); + // cpu 信息 + resultMap.put("cpu", getCpuInfo(hal.getProcessor())); + // 内存信息 + resultMap.put("memory", getMemoryInfo(hal.getMemory())); + // 交换区信息 + resultMap.put("swap", getSwapInfo(hal.getMemory())); + // 磁盘 + resultMap.put("disk", getDiskInfo(os)); + resultMap.put("time", DateUtil.format(new Date(), "HH:mm:ss")); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return resultMap; + } + + /** + * 获取磁盘信息 + * + * @return / + */ + private Map getDiskInfo(OperatingSystem os) { + Map diskInfo = new LinkedHashMap<>(); + FileSystem fileSystem = os.getFileSystem(); + List fsArray = fileSystem.getFileStores(); + String osName = System.getProperty("os.name"); + long available = 0, total = 0; + for (OSFileStore fs : fsArray) { + // windows 需要将所有磁盘分区累加, linux 和 mac 直接累加会出现磁盘重复的问题, 待修复 + if (osName.toLowerCase().startsWith(SystemConst.WIN)) { + available += fs.getUsableSpace(); + total += fs.getTotalSpace(); + } else { + available = fs.getUsableSpace(); + total = fs.getTotalSpace(); + break; + } + } + long used = total - available; + diskInfo.put("total", total > 0 ? CsFileUtil.getSize(total) : "?"); + diskInfo.put("available", CsFileUtil.getSize(available)); + diskInfo.put("used", CsFileUtil.getSize(used)); + if (total != 0) { + diskInfo.put("usageRate", df.format(used / (double) total * 100)); + } else { + diskInfo.put("usageRate", 0); + } + return diskInfo; + } + + /** + * 获取交换区信息 + * + * @param memory / + * @return / + */ + private Map getSwapInfo(GlobalMemory memory) { + Map swapInfo = new LinkedHashMap<>(); + VirtualMemory virtualMemory = memory.getVirtualMemory(); + long total = virtualMemory.getSwapTotal(); + long used = virtualMemory.getSwapUsed(); + swapInfo.put("total", FormatUtil.formatBytes(total)); + swapInfo.put("used", FormatUtil.formatBytes(used)); + swapInfo.put("available", FormatUtil.formatBytes(total - used)); + if (used == 0) { + swapInfo.put("usageRate", 0); + } else { + swapInfo.put("usageRate", df.format(used / (double) total * 100)); + } + return swapInfo; + } + + /** + * 获取内存信息 + * + * @param memory / + * @return / + */ + private Map getMemoryInfo(GlobalMemory memory) { + Map memoryInfo = new LinkedHashMap<>(); + memoryInfo.put("total", FormatUtil.formatBytes(memory.getTotal())); + memoryInfo.put("available", FormatUtil.formatBytes(memory.getAvailable())); + memoryInfo.put("used", FormatUtil.formatBytes(memory.getTotal() - memory.getAvailable())); + memoryInfo.put( + "usageRate", + df.format((memory.getTotal() - memory.getAvailable()) / (double) memory.getTotal() * 100)); + return memoryInfo; + } + + /** + * 获取Cpu相关信息 + * + * @param processor / + * @return / + */ + private Map getCpuInfo(CentralProcessor processor) { + Map cpuInfo = new LinkedHashMap<>(); + cpuInfo.put("name", processor.getProcessorIdentifier().getName()); + cpuInfo.put("package", processor.getPhysicalPackageCount() + "个物理CPU"); + cpuInfo.put("core", processor.getPhysicalProcessorCount() + "个物理核心"); + cpuInfo.put("coreNumber", processor.getPhysicalProcessorCount()); + cpuInfo.put("logic", processor.getLogicalProcessorCount() + "个逻辑CPU"); + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + // 默认等待300毫秒... + long time = 300; + Util.sleep(time); + long[] ticks = processor.getSystemCpuLoadTicks(); + long minCpuLoadTick = 1000; + while (Arrays.toString(prevTicks).equals(Arrays.toString(ticks)) && time < minCpuLoadTick) { + time += 25; + Util.sleep(25); + ticks = processor.getSystemCpuLoadTicks(); + } + long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()]; + long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()]; + long sys = + ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()]; + long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()]; + long ioWait = + ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()]; + long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()]; + long softIrq = + ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()]; + long steal = + ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()]; + long totalCpu = user + nice + sys + idle + ioWait + irq + softIrq + steal; + cpuInfo.put("used", df.format(100d * user / totalCpu + 100d * sys / totalCpu)); + cpuInfo.put("idle", df.format(100d * idle / totalCpu)); + return cpuInfo; + } + + /** + * 获取系统相关信息,系统、运行天数、系统IP + * + * @param os / + * @return / + */ + private Map getSystemInfo(OperatingSystem os) { + Map systemInfo = new LinkedHashMap<>(); + // jvm 运行时间 + long time = ManagementFactory.getRuntimeMXBean().getStartTime(); + Date date = new Date(time); + // 计算项目运行时间 + String formatBetween = DateUtil.formatBetween(date, new Date(), Level.HOUR); + // 系统信息 + systemInfo.put("os", os.toString()); + systemInfo.put("day", formatBetween); + systemInfo.put("ip", CsIPUtil.getLocalIp()); + return systemInfo; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemOssStorageService.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemOssStorageService.java new file mode 100644 index 00000000..26fafc9a --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemOssStorageService.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service; + +import cn.odboy.base.CsPageResult; +import cn.odboy.system.dal.dataobject.SystemOssStorageTb; +import cn.odboy.system.dal.model.SystemOssStorageVo; +import cn.odboy.system.dal.model.SystemQueryStorageArgs; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + *

+ * OSS存储 服务类 + *

+ * + * @author codegen + * @since 2025-07-15 + */ +public interface SystemOssStorageService extends IService { + CsPageResult queryOssStorage(SystemQueryStorageArgs criteria, Page page); + + List queryOssStorage(SystemQueryStorageArgs criteria); + + void exportOssStorageExcel(List list, HttpServletResponse response) throws IOException; + + String uploadFile(MultipartFile file); + + void removeFileByIds(Long[] ids); + + SystemOssStorageTb getByMd5(String md5); +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemQuartzJobService.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemQuartzJobService.java new file mode 100644 index 00000000..e6242230 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemQuartzJobService.java @@ -0,0 +1,283 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.CsPageResult; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.redis.CsRedisHelper; +import cn.odboy.system.dal.dataobject.SystemQuartzJobTb; +import cn.odboy.system.dal.dataobject.SystemQuartzLogTb; +import cn.odboy.system.dal.model.SystemQueryQuartzJobArgs; +import cn.odboy.system.dal.model.SystemUpdateQuartzJobArgs; +import cn.odboy.system.dal.mysql.SystemQuartzJobMapper; +import cn.odboy.system.dal.mysql.SystemQuartzLogMapper; +import cn.odboy.system.framework.quartz.QuartzManage; +import cn.odboy.util.CsFileUtil; +import cn.odboy.util.CsPageUtil; +import cn.odboy.util.CsStringUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.quartz.CronExpression; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + +@Service +public class SystemQuartzJobService { + @Autowired + private SystemQuartzJobMapper systemQuartzJobMapper; + @Autowired + private SystemQuartzLogMapper systemQuartzLogMapper; + @Autowired + private QuartzManage quartzManage; + @Autowired + private CsRedisHelper redisHelper; + + /** + * 创建 + * + * @param args / + */ + + @Transactional(rollbackFor = Exception.class) + public void createJob(SystemQuartzJobTb args) { + if (!CronExpression.isValidExpression(args.getCronExpression())) { + throw new BadRequestException("cron表达式格式错误"); + } + systemQuartzJobMapper.insert(args); + quartzManage.addJob(args); + } + + /** + * 修改任务并重新调度 + * + * @param args / + */ + + @Transactional(rollbackFor = Exception.class) + public void modifyQuartzJobResumeCron(SystemUpdateQuartzJobArgs args) { + if (!CronExpression.isValidExpression(args.getCronExpression())) { + throw new BadRequestException("cron表达式格式错误"); + } + if (StrUtil.isNotBlank(args.getSubTask())) { + List tasks = Arrays.asList(args.getSubTask().split("[,,]")); + if (tasks.contains(args.getId().toString())) { + throw new BadRequestException("子任务中不能添加当前任务ID"); + } + } + SystemQuartzJobTb quartzJob = BeanUtil.copyProperties(args, SystemQuartzJobTb.class); + systemQuartzJobMapper.insertOrUpdate(quartzJob); + quartzManage.updateJobCron(quartzJob); + } + + /** + * 更改定时任务状态 + * + * @param quartzJob / + */ + + @Transactional(rollbackFor = Exception.class) + public void switchQuartzJobStatus(SystemQuartzJobTb quartzJob) { + // 置换暂停状态 + if (quartzJob.getIsPause()) { + quartzManage.resumeJob(quartzJob); + quartzJob.setIsPause(false); + } else { + quartzManage.pauseJob(quartzJob); + quartzJob.setIsPause(true); + } + systemQuartzJobMapper.insertOrUpdate(quartzJob); + } + + /** + * 立即执行定时任务 + * + * @param quartzJob / + */ + + public void startQuartzJob(SystemQuartzJobTb quartzJob) { + quartzManage.runJobNow(quartzJob); + } + + /** + * 删除任务 + * + * @param ids / + */ + + @Transactional(rollbackFor = Exception.class) + public void removeJobByIds(Set ids) { + for (Long id : ids) { + SystemQuartzJobTb quartzJob = systemQuartzJobMapper.selectById(id); + quartzManage.deleteJob(quartzJob); + systemQuartzJobMapper.deleteById(quartzJob); + } + } + + /** + * 执行子任务 + * + * @param tasks / + * @throws InterruptedException / + */ + + @Transactional(rollbackFor = Exception.class) + public void startSubQuartJob(String[] tasks) throws InterruptedException { + for (String id : tasks) { + if (StrUtil.isBlank(id)) { + // 如果是手动清除子任务id, 会出现id为空字符串的问题 + continue; + } + SystemQuartzJobTb quartzJob = systemQuartzJobMapper.selectById(Long.parseLong(id)); + if (quartzJob == null) { + // 防止子任务不存在 + continue; + } + // 执行任务 + String uuid = IdUtil.simpleUUID(); + quartzJob.setUuid(uuid); + // 执行任务 + startQuartzJob(quartzJob); + // 获取执行状态, 如果执行失败则停止后面的子任务执行 + Boolean result = redisHelper.get(uuid, Boolean.class); + while (result == null) { + // 休眠5秒, 再次获取子任务执行情况 + Thread.sleep(5000); + result = redisHelper.get(uuid, Boolean.class); + } + if (!result) { + redisHelper.del(uuid); + break; + } + } + } + + /** + * 导出定时任务 + * + * @param quartzJobs 待导出的数据 + * @param response / + * @throws IOException / + */ + + public void exportQuartzJobExcel(List quartzJobs, HttpServletResponse response) + throws IOException { + List> list = new ArrayList<>(); + for (SystemQuartzJobTb quartzJob : quartzJobs) { + Map map = new LinkedHashMap<>(); + map.put("任务名称", quartzJob.getJobName()); + map.put("Bean名称", quartzJob.getBeanName()); + map.put("执行方法", quartzJob.getMethodName()); + map.put("参数", quartzJob.getParams()); + map.put("表达式", quartzJob.getCronExpression()); + map.put("状态", quartzJob.getIsPause() ? "暂停中" : "运行中"); + map.put("描述", quartzJob.getDescription()); + map.put("创建日期", quartzJob.getCreateTime()); + list.add(map); + } + CsFileUtil.downloadExcel(list, response); + } + + /** + * 导出定时任务日志 + * + * @param queryAllLog 待导出的数据 + * @param response / + * @throws IOException / + */ + + public void exportQuartzLogExcel(List queryAllLog, HttpServletResponse response) + throws IOException { + List> list = new ArrayList<>(); + for (SystemQuartzLogTb quartzLog : queryAllLog) { + Map map = new LinkedHashMap<>(); + map.put("任务名称", quartzLog.getJobName()); + map.put("Bean名称", quartzLog.getBeanName()); + map.put("执行方法", quartzLog.getMethodName()); + map.put("参数", quartzLog.getParams()); + map.put("表达式", quartzLog.getCronExpression()); + map.put("异常详情", quartzLog.getExceptionDetail()); + map.put("耗时/毫秒", quartzLog.getTime()); + map.put("状态", quartzLog.getIsSuccess() ? "成功" : "失败"); + map.put("创建日期", quartzLog.getCreateTime()); + list.add(map); + } + CsFileUtil.downloadExcel(list, response); + } + + /** + * 分页查询 + * + * @param criteria 条件 + * @param page 分页参数 + * @return / + */ + + public CsPageResult queryQuartzJobByArgs( + SystemQueryQuartzJobArgs criteria, + Page page) { + return CsPageUtil.toPage(systemQuartzJobMapper.selectQuartzJobByArgs(criteria, page)); + } + + /** + * 分页查询日志 + * + * @param criteria 条件 + * @param page 分页参数 + * @return / + */ + + public CsPageResult queryQuartzLogByArgs( + SystemQueryQuartzJobArgs criteria, + Page page) { + return CsPageUtil.toPage(systemQuartzLogMapper.selectQuartzLogByArgs(criteria, page)); + } + + /** + * 查询全部 + * + * @param criteria 条件 + * @return / + */ + + public List queryQuartzJobByArgs(SystemQueryQuartzJobArgs criteria) { + return systemQuartzJobMapper.selectQuartzJobByArgs(criteria, CsPageUtil.getCount(systemQuartzJobMapper)) + .getRecords(); + } + + /** + * 查询全部 + * + * @param criteria 条件 + * @return / + */ + + public List queryQuartzLogByArgs(SystemQueryQuartzJobArgs criteria) { + return systemQuartzLogMapper.selectQuartzLogByArgs(criteria, CsPageUtil.getCount(systemQuartzLogMapper)) + .getRecords(); + } + + public SystemQuartzJobTb getQuartzJobById(Long id) { + return systemQuartzJobMapper.selectById(id); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleService.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleService.java new file mode 100644 index 00000000..35010e66 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleService.java @@ -0,0 +1,264 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.CsPageResult; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.system.dal.dataobject.SystemMenuTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import cn.odboy.system.dal.dataobject.SystemUserTb; +import cn.odboy.system.dal.model.SystemCreateRoleArgs; +import cn.odboy.system.dal.model.SystemQueryRoleArgs; +import cn.odboy.system.dal.model.SystemRoleCodeVo; +import cn.odboy.system.dal.mysql.SystemRoleDeptMapper; +import cn.odboy.system.dal.mysql.SystemRoleMapper; +import cn.odboy.system.dal.mysql.SystemRoleMenuMapper; +import cn.odboy.system.dal.mysql.SystemUserMapper; +import cn.odboy.util.CsFileUtil; +import cn.odboy.util.CsPageUtil; +import cn.odboy.util.CsStringUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class SystemRoleService { + @Autowired + private SystemRoleMapper systemRoleMapper; + @Autowired + private SystemRoleDeptMapper systemRoleDeptMapper; + @Autowired + private SystemRoleMenuMapper systemRoleMenuMapper; + @Autowired + private SystemUserMapper systemUserMapper; + + /** + * 创建 + * + * @param args / + */ + + @Transactional(rollbackFor = Exception.class) + public void saveRole(SystemCreateRoleArgs args) { + if (systemRoleMapper.getRoleByName(args.getName()) != null) { + throw new BadRequestException("角色名称已存在"); + } + systemRoleMapper.insert(BeanUtil.copyProperties(args, SystemRoleTb.class)); + // 判断是否有部门数据, 若有, 则需创建关联 + if (CollectionUtil.isNotEmpty(args.getDepts())) { + systemRoleDeptMapper.batchInsertRoleDept(args.getDepts(), args.getId()); + } + } + + /** + * 修改 + * + * @param args / + */ + + @Transactional(rollbackFor = Exception.class) + public void modifyRoleById(SystemRoleTb args) { + SystemRoleTb role = systemRoleMapper.selectById(args.getId()); + SystemRoleTb role1 = systemRoleMapper.getRoleByName(args.getName()); + if (role1 != null && !role1.getId().equals(role.getId())) { + throw new BadRequestException("角色名称已存在"); + } + role.setName(args.getName()); + role.setDescription(args.getDescription()); + role.setDataScope(args.getDataScope()); + role.setDepts(args.getDepts()); + role.setLevel(args.getLevel()); + // 更新 + systemRoleMapper.insertOrUpdate(role); + // 删除关联部门数据 + systemRoleDeptMapper.batchDeleteRoleDept(Collections.singleton(args.getId())); + // 判断是否有部门数据, 若有, 则需更新关联 + if (CollectionUtil.isNotEmpty(args.getDepts())) { + systemRoleDeptMapper.batchInsertRoleDept(args.getDepts(), args.getId()); + } + } + + /** + * 修改绑定的菜单 + * + * @param role / + */ + + public void modifyBindMenuById(SystemRoleTb role) { + // 更新菜单 + systemRoleMenuMapper.deleteRoleMenuByRoleId(role.getId()); + // 判断是否为空 + if (CollUtil.isNotEmpty(role.getMenus())) { + systemRoleMenuMapper.batchInsertRoleMenu(role.getMenus(), role.getId()); + } + } + + /** + * 删除 + * + * @param ids / + */ + + @Transactional(rollbackFor = Exception.class) + public void removeRoleByIds(Set ids) { + systemRoleMapper.deleteByIds(ids); + // 删除角色部门关联数据、角色菜单关联数据 + systemRoleDeptMapper.batchDeleteRoleDept(ids); + systemRoleMenuMapper.batchDeleteRoleMenu(ids); + } + + /** + * 导出数据 + * + * @param roles 待导出的数据 + * @param response / + * @throws IOException + */ + + public void exportRoleExcel(List roles, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (SystemRoleTb role : roles) { + Map map = new LinkedHashMap<>(); + map.put("角色名称", role.getName()); + map.put("角色级别", role.getLevel()); + map.put("描述", role.getDescription()); + map.put("创建日期", role.getCreateTime()); + list.add(map); + } + CsFileUtil.downloadExcel(list, response); + } + + /** + * 查询全部角色 + * + * @return + */ + + public List queryAllRole() { + return systemRoleMapper.selectAllRole(); + } + + /** + * 根据条件查询全部角色 + * + * @param criteria 条件 + * @return + */ + + public List queryRoleByArgs(SystemQueryRoleArgs criteria) { + return systemRoleMapper.selectRoleByArgs(criteria); + } + + /** + * 分页查询角色 + * + * @param criteria 条件 + * @param page 分页参数 + * @return + */ + + public CsPageResult queryRoleByArgs(SystemQueryRoleArgs criteria, Page page) { + criteria.setOffset(page.offset()); + List roles = systemRoleMapper.selectRoleByArgs(criteria); + Long total = systemRoleMapper.countRoleByArgs(criteria); + return CsPageUtil.toPage(roles, total); + } + + /** + * 根据用户ID查询 + * + * @param userId 用户ID + * @return / + */ + + public List queryRoleByUsersId(Long userId) { + return systemRoleMapper.selectRoleByUserId(userId); + } + + /** + * 根据角色查询角色级别 + * + * @param roles / + * @return / + */ + + public Integer getDeptLevelByRoles(Set roles) { + if (CollUtil.isEmpty(roles)) { + return Integer.MAX_VALUE; + } + Set roleSet = new HashSet<>(); + for (SystemRoleTb role : roles) { + roleSet.add(getRoleById(role.getId())); + } + return Collections.min(roleSet.stream().map(SystemRoleTb::getLevel).collect(Collectors.toList())); + } + + /** + * 获取用户权限信息 + * + * @param user 用户信息 + * @return 权限信息 + */ + + public List buildUserRolePermissions(SystemUserTb user) { + Set permissions = new HashSet<>(); + // 如果是管理员直接返回 + if (user.getIsAdmin()) { + permissions.add("admin"); + return permissions.stream().map(SystemRoleCodeVo::new).collect(Collectors.toList()); + } + List roles = systemRoleMapper.selectRoleByUserId(user.getId()); + permissions = roles.stream() + .flatMap(role -> role.getMenus().stream()) + .map(SystemMenuTb::getPermission) + .filter(StrUtil::isNotBlank) + .collect(Collectors.toSet()); + return permissions.stream().map(SystemRoleCodeVo::new).collect(Collectors.toList()); + } + + /** + * 验证是否被用户关联 + * + * @param ids / + */ + + public void verifyBindRelationByIds(Set ids) { + if (systemUserMapper.countUserByRoleIds(ids) > 0) { + throw new BadRequestException("所选角色存在用户关联, 请解除关联再试!"); + } + } + + /** + * 根据ID查询 + * + * @param id / + * @return / + */ + public SystemRoleTb getRoleById(Long id) { + return systemRoleMapper.selectById(id); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserService.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserService.java new file mode 100644 index 00000000..16752249 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserService.java @@ -0,0 +1,346 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service; + +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.CsPageResult; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.server.core.CsFileLocalUploadHelper; +import cn.odboy.system.dal.dataobject.SystemJobTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import cn.odboy.system.dal.dataobject.SystemUserTb; +import cn.odboy.system.dal.model.SystemQueryUserArgs; +import cn.odboy.system.dal.mysql.SystemUserJobMapper; +import cn.odboy.system.dal.mysql.SystemUserMapper; +import cn.odboy.system.dal.mysql.SystemUserRoleMapper; +import cn.odboy.system.dal.redis.SystemUserInfoDAO; +import cn.odboy.system.dal.redis.SystemUserOnlineInfoDAO; +import cn.odboy.system.framework.permission.core.CsSecurityHelper; +import cn.odboy.util.CsFileUtil; +import cn.odboy.util.CsPageUtil; +import cn.odboy.util.CsStringUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class SystemUserService { + @Autowired + private SystemUserMapper systemUserMapper; + @Autowired + private SystemUserJobMapper systemUserJobMapper; + @Autowired + private SystemUserRoleMapper systemUserRoleMapper; + @Autowired + private SystemUserInfoDAO systemUserInfoDAO; + @Autowired + private SystemUserOnlineInfoDAO systemUserOnlineInfoDAO; + @Autowired + private CsFileLocalUploadHelper fileUploadPathHelper; + + /** + * 新增用户 + * + * @param args / + */ + + @Transactional(rollbackFor = Exception.class) + public void saveUser(SystemUserTb args) { + args.setDeptId(args.getDept().getId()); + if (systemUserMapper.getUserByUsername(args.getUsername()) != null) { + throw new BadRequestException("用户名已存在"); + } + if (systemUserMapper.getUserByEmail(args.getEmail()) != null) { + throw new BadRequestException("邮箱已存在"); + } + if (systemUserMapper.getUserByPhone(args.getPhone()) != null) { + throw new BadRequestException("手机号已存在"); + } + systemUserMapper.insert(args); + // 保存用户岗位 + systemUserJobMapper.batchInsertUserJob(args.getJobs(), args.getId()); + // 保存用户角色 + systemUserRoleMapper.batchInsertUserRole(args.getRoles(), args.getId()); + } + + /** + * 编辑用户 + * + * @param args / + * @throws Exception / + */ + + @Transactional(rollbackFor = Exception.class) + public void modifyUserById(SystemUserTb args) { + SystemUserTb user = systemUserMapper.selectById(args.getId()); + SystemUserTb user1 = systemUserMapper.getUserByUsername(args.getUsername()); + SystemUserTb user2 = systemUserMapper.getUserByEmail(args.getEmail()); + SystemUserTb user3 = systemUserMapper.getUserByPhone(args.getPhone()); + if (user1 != null && !user.getId().equals(user1.getId())) { + throw new BadRequestException("用户名已存在"); + } + if (user2 != null && !user.getId().equals(user2.getId())) { + throw new BadRequestException("邮箱已存在"); + } + if (user3 != null && !user.getId().equals(user3.getId())) { + throw new BadRequestException("手机号已存在"); + } + // 如果用户被禁用, 则清除用户登录信息 + if (!args.getEnabled()) { + systemUserOnlineInfoDAO.kickOutByUsername(args.getUsername()); + } + user.setDeptId(args.getDept().getId()); + user.setUsername(args.getUsername()); + user.setEmail(args.getEmail()); + user.setEnabled(args.getEnabled()); + user.setRoles(args.getRoles()); + user.setDept(args.getDept()); + user.setJobs(args.getJobs()); + user.setPhone(args.getPhone()); + user.setNickName(args.getNickName()); + user.setGender(args.getGender()); + systemUserMapper.insertOrUpdate(user); + // 清除用户登录缓存 + systemUserInfoDAO.deleteUserLoginInfoByUserName(user.getUsername()); + // 更新用户岗位 + systemUserJobMapper.batchDeleteUserJob(Collections.singleton(args.getId())); + systemUserJobMapper.batchInsertUserJob(args.getJobs(), args.getId()); + // 更新用户角色 + systemUserRoleMapper.batchDeleteUserRole(Collections.singleton(args.getId())); + systemUserRoleMapper.batchInsertUserRole(args.getRoles(), args.getId()); + } + + /** + * 用户自助修改资料 + * + * @param args / + */ + + @Transactional(rollbackFor = Exception.class) + public void modifyUserCenterInfoById(SystemUserTb args) { + SystemUserTb user = systemUserMapper.selectById(args.getId()); + SystemUserTb user1 = systemUserMapper.getUserByPhone(args.getPhone()); + if (user1 != null && !user.getId().equals(user1.getId())) { + throw new BadRequestException("手机号已存在"); + } + user.setNickName(args.getNickName()); + user.setPhone(args.getPhone()); + user.setGender(args.getGender()); + systemUserMapper.insertOrUpdate(user); + systemUserInfoDAO.deleteUserLoginInfoByUserName(user.getUsername()); + } + + /** + * 删除用户 + * + * @param ids / + */ + + @Transactional(rollbackFor = Exception.class) + public void removeUserByIds(Set ids) { + for (Long id : ids) { + // 清理缓存 + SystemUserTb user = systemUserMapper.selectById(id); + systemUserInfoDAO.deleteUserLoginInfoByUserName(user.getUsername()); + } + systemUserMapper.deleteByIds(ids); + // 删除用户岗位 + systemUserJobMapper.batchDeleteUserJob(ids); + // 删除用户角色 + systemUserRoleMapper.batchDeleteUserRole(ids); + } + + /** + * 修改密码 + * + * @param username 用户名 + * @param encryptPassword 密码 + */ + + @Transactional(rollbackFor = Exception.class) + public void modifyUserPasswordByUsername(String username, String encryptPassword) { + systemUserMapper.updateUserPasswordByUsername(username, encryptPassword); + systemUserInfoDAO.deleteUserLoginInfoByUserName(username); + } + + /** + * 重置密码 + * + * @param ids 用户id + * @param password 密码 + */ + + @Transactional(rollbackFor = Exception.class) + public void resetUserPasswordByIds(Set ids, String password) { + List users = systemUserMapper.selectByIds(ids); + // 清除缓存 + users.forEach(user -> { + // 清除缓存 + systemUserInfoDAO.deleteUserLoginInfoByUserName(user.getUsername()); + // 强制退出 + systemUserOnlineInfoDAO.kickOutByUsername(user.getUsername()); + }); + // 重置密码 + systemUserMapper.batchUpdatePassword(password, ids); + } + + /** + * 修改头像 + * + * @param multipartFile 文件 + * @return / + */ + + @Transactional(rollbackFor = Exception.class) + public Map modifyUserAvatar(MultipartFile multipartFile) { + SystemUserTb user = systemUserMapper.getUserByUsername(CsSecurityHelper.getCurrentUsername()); + String username = user.getUsername(); + if (StrUtil.isBlank(username)) { + throw new BadRequestException("异常用户数据, 请联系管理员处理"); + } + // 文件大小验证 + CsFileUtil.checkSize(fileUploadPathHelper.getAvatarMaxSize(), multipartFile.getSize()); + // 验证文件上传的格式 + String image = "gif jpg png jpeg"; + String fileType = CsFileUtil.getSuffix(multipartFile.getOriginalFilename()); + if (fileType != null && !image.contains(fileType)) { + throw new BadRequestException("文件格式错误!, 仅支持 " + image + " 格式"); + } + String oldPath = user.getAvatarPath(); + File file = CsFileUtil.upload(multipartFile, fileUploadPathHelper.getPath()); + user.setAvatarPath(Objects.requireNonNull(file).getPath()); + user.setAvatarName(file.getName()); + systemUserMapper.insertOrUpdate(user); + if (StrUtil.isNotBlank(oldPath)) { + CsFileUtil.del(oldPath); + } + systemUserInfoDAO.deleteUserLoginInfoByUserName(username); + return new HashMap<>(1) {{ + put("avatar", file.getName()); + }}; + } + + /** + * 修改邮箱 + * + * @param username 用户名 + * @param email 邮箱 + */ + + @Transactional(rollbackFor = Exception.class) + public void modifyUserEmailByUsername(String username, String email) { + systemUserMapper.updateUserEmailByUsername(username, email); + systemUserInfoDAO.deleteUserLoginInfoByUserName(username); + } + + /** + * 导出数据 + * + * @param users 待导出的数据 + * @param response / + * @throws IOException / + */ + + public void exportUserExcel(List users, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (SystemUserTb user : users) { + List roles = user.getRoles().stream().map(SystemRoleTb::getName).collect(Collectors.toList()); + Map map = new LinkedHashMap<>(); + map.put("用户名", user.getUsername()); + map.put("角色", roles); + map.put("部门", user.getDept().getName()); + map.put("岗位", user.getJobs().stream().map(SystemJobTb::getName).collect(Collectors.toList())); + map.put("邮箱", user.getEmail()); + map.put("状态", user.getEnabled() ? "启用" : "禁用"); + map.put("手机号码", user.getPhone()); + map.put("修改密码的时间", user.getPwdResetTime()); + map.put("创建日期", user.getCreateTime()); + list.add(map); + } + CsFileUtil.downloadExcel(list, response); + } + + /** + * 查询全部 + * + * @param criteria 条件 + * @param page 分页参数 + * @return / + */ + + public CsPageResult queryUserByArgs(SystemQueryUserArgs criteria, Page page) { + IPage users = systemUserMapper.selectUserByArgs(criteria, page); + return CsPageUtil.toPage(users); + } + + /** + * 查询全部不分页 + * + * @param criteria 条件 + * @return / + */ + + public List queryUserByArgs(SystemQueryUserArgs criteria) { + return systemUserMapper.selectUserByArgs(criteria); + } + + /** + * 根据ID查询 + * + * @param id ID + * @return / + */ + + public SystemUserTb getUserById(long id) { + return systemUserMapper.selectById(id); + } + + /** + * 根据用户名查询 + * + * @param username / + * @return / + */ + public SystemUserTb getUserByUsername(String username) { + return systemUserMapper.getUserByUsername(username); + } + + /** + * 分页模糊查询 + * + * @param wrapper + * @param page + * @return + */ + public IPage queryUserByBlurry(LambdaQueryWrapper wrapper, Page page) { + return systemUserMapper.selectPage(page, wrapper); + } + + public List queryUserByUserIds(List userIds) { + return systemUserMapper.selectByIds(userIds); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/impl/SystemOssStorageServiceImpl.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/impl/SystemOssStorageServiceImpl.java new file mode 100644 index 00000000..860d7077 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/service/impl/SystemOssStorageServiceImpl.java @@ -0,0 +1,157 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.CsPageResult; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.properties.model.StorageOSSModel; +import cn.odboy.framework.server.core.CsFileLocalUploadHelper; +import cn.odboy.system.dal.dataobject.SystemOssStorageTb; +import cn.odboy.system.dal.model.SystemOssStorageVo; +import cn.odboy.system.dal.model.SystemQueryStorageArgs; +import cn.odboy.system.dal.mysql.SystemOssStorageMapper; +import cn.odboy.system.framework.storage.minio.MinioRepository; +import cn.odboy.system.service.SystemOssStorageService; +import cn.odboy.util.CsDateUtil; +import cn.odboy.util.CsFileUtil; +import cn.odboy.util.CsPageUtil; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + *

+ * OSS存储 服务实现类 + *

+ * + * @author codegen + * @since 2025-07-15 + */ +@Service +public class SystemOssStorageServiceImpl extends ServiceImpl implements SystemOssStorageService { + @Autowired + private MinioRepository minioRepository; + @Autowired + private AppProperties properties; + @Autowired + private CsFileLocalUploadHelper fileLocalUploadHelper; + + @Override + public CsPageResult queryOssStorage(SystemQueryStorageArgs criteria, Page page) { + IPage ossStorageTbs = baseMapper.selectOssStorageByArgs(criteria, page); + IPage convert = ossStorageTbs.convert(c -> { + SystemOssStorageVo storageVo = BeanUtil.copyProperties(c, SystemOssStorageVo.class); + storageVo.setFileSizeDesc(CsFileUtil.getSize(storageVo.getFileSize())); + return storageVo; + }); + return CsPageUtil.toPage(convert); + } + + @Override + public List queryOssStorage(SystemQueryStorageArgs criteria) { + // 防止刷数据 + Page page = new Page<>(1, 500); + return queryOssStorage(criteria, page).getContent(); + } + + @Override + public void exportOssStorageExcel(List ossStorages, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (SystemOssStorageVo ossStorage : ossStorages) { + Map map = new LinkedHashMap<>(); + map.put("服务类型", ossStorage.getServiceType()); + map.put("服务地址", ossStorage.getEndpoint()); + map.put("存储桶名称", ossStorage.getBucketName()); + map.put("文件名称", ossStorage.getFileName()); + map.put("文件大小", ossStorage.getFileSizeDesc()); + map.put("文件类型", ossStorage.getFileMime()); + map.put("创建人", ossStorage.getCreateBy()); + map.put("创建日期", ossStorage.getCreateTime()); + list.add(map); + } + CsFileUtil.downloadExcel(list, response); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String uploadFile(MultipartFile file) { + String originalFilename = file.getOriginalFilename(); + if (StrUtil.isBlank(originalFilename)) { + throw new BadRequestException("文件名不能为空"); + } + StorageOSSModel ossConfig = properties.getOss(); + long fileSize = file.getSize(); + String contentType = file.getContentType(); + CsFileUtil.checkSize(ossConfig.getMaxSize(), fileSize); + // 按天分组 + String nowDateStr = CsDateUtil.getNowDateStr(); + // 上传到本地临时目录 + File tempFile = CsFileUtil.upload(file, fileLocalUploadHelper.getPath() + nowDateStr + File.separator); + if (tempFile == null) { + throw new BadRequestException("上传失败"); + } + // 校验文件md5, 看是否已存在云端(不确定, 可能云端已经删除, 但是正常来说云端是不允许私自删除的, 所以这里忽略云端不存在的情况) + String md5 = CsFileUtil.getMd5(tempFile); + SystemOssStorageTb systemOssStorageTb = getByMd5(md5); + if (systemOssStorageTb != null) { + // 重新生成7天链接 + return minioRepository.generatePreviewUrl(systemOssStorageTb.getObjectName()); + } + // 上传到OSS + SystemOssStorageTb storageTb = minioRepository.upload(tempFile, originalFilename, fileSize, contentType, md5); + if (storageTb == null) { + throw new BadRequestException("文件上传失败"); + } + save(storageTb); + return minioRepository.generatePreviewUrl(storageTb.getObjectName()); + } + + /** + * 多选删除 + * + * @param ids / + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void removeFileByIds(Long[] ids) { + for (Long id : ids) { + SystemOssStorageTb storage = getById(id); + minioRepository.removeBucketFile(storage.getObjectName()); + removeById(storage); + } + } + + @Override + public SystemOssStorageTb getByMd5(String md5) { + return lambdaQuery().eq(SystemOssStorageTb::getFileMd5, md5).one(); + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/util/SqlUtil.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/util/SqlUtil.java new file mode 100644 index 00000000..0f4d88af --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/util/SqlUtil.java @@ -0,0 +1,188 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.system.util; + +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.mybatisplus.config.DataTypeEnum; +import cn.odboy.util.CsCloseUtil; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.google.common.collect.Lists; +import com.zaxxer.hikari.HikariDataSource; +import lombok.extern.slf4j.Slf4j; + +import javax.sql.DataSource; +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; + +@Slf4j +public final class SqlUtil { + /** + * 获取数据源 + * + * @param jdbcUrl / + * @param userName / + * @param password / + * @return DataSource + */ + private static DataSource getDataSource(String jdbcUrl, String userName, String password) { + HikariDataSource druidDataSource = new HikariDataSource(); + String className; + try { + className = DriverManager.getDriver(jdbcUrl.trim()).getClass().getName(); + } catch (SQLException e) { + throw new BadRequestException("Get class name error: =" + jdbcUrl); + } + if (StringUtils.isEmpty(className)) { + DataTypeEnum dataTypeEnum = DataTypeEnum.urlOf(jdbcUrl); + if (null == dataTypeEnum) { + throw new BadRequestException("Not supported data type: jdbcUrl=" + jdbcUrl); + } + druidDataSource.setDriverClassName(dataTypeEnum.getDriver()); + } else { + druidDataSource.setDriverClassName(className); + } + druidDataSource.setJdbcUrl(jdbcUrl); + druidDataSource.setUsername(userName); + druidDataSource.setPassword(password); + druidDataSource.setConnectionTimeout(3000); + return druidDataSource; + } + + private static Connection getConnection(String jdbcUrl, String userName, String password) { + DataSource dataSource = getDataSource(jdbcUrl, userName, password); + Connection connection = null; + try { + connection = dataSource.getConnection(); + } catch (Exception ignored) { + } + try { + int timeOut = 5; + if (null == connection || connection.isClosed() || !connection.isValid(timeOut)) { + log.info("connection is closed or invalid, retry get connection!"); + connection = dataSource.getConnection(); + } + } catch (Exception e) { + log.error("create connection error, jdbcUrl: {}", jdbcUrl); + throw new BadRequestException("create connection error, jdbcUrl: " + jdbcUrl); + } finally { + CsCloseUtil.close(connection); + } + return connection; + } + + private static void releaseConnection(Connection connection) { + if (null != connection) { + try { + connection.close(); + } catch (Exception e) { + log.error(e.getMessage(), e); + log.error("connection close error:" + e.getMessage()); + } + } + } + + public static boolean testConnection(String jdbcUrl, String userName, String password) { + Connection connection = null; + try { + connection = getConnection(jdbcUrl, userName, password); + if (null != connection) { + return true; + } + } catch (Exception e) { + log.info("Get connection failed:" + e.getMessage()); + } finally { + releaseConnection(connection); + } + return false; + } + + public static String executeFile(String jdbcUrl, String username, String password, File sqlFile) { + Connection connection = getConnection(jdbcUrl, username, password); + try { + batchExecute(connection, readSqlList(sqlFile)); + } catch (Exception e) { + log.error("sql脚本执行发生异常:{}", e.getMessage()); + return e.getMessage(); + } finally { + releaseConnection(connection); + } + return "success"; + } + + /** + * 批量执行sql + * + * @param connection / + * @param sqlList / + */ + public static void batchExecute(Connection connection, List sqlList) { + Statement st = null; + try { + st = connection.createStatement(); + for (String sql : sqlList) { + if (sql.endsWith(";")) { + sql = sql.substring(0, sql.length() - 1); + } + st.addBatch(sql); + } + st.executeBatch(); + } catch (SQLException throwables) { + throwables.printStackTrace(); + } finally { + CsCloseUtil.close(st); + } + } + + /** + * 将文件中的sql语句以;为单位读取到列表中 + * + * @param sqlFile / + * @return / + * @throws Exception e + */ + private static List readSqlList(File sqlFile) throws Exception { + List sqlList = Lists.newArrayList(); + StringBuilder sb = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader( + Files.newInputStream(sqlFile.toPath()), + StandardCharsets.UTF_8))) { + String tmp; + while ((tmp = reader.readLine()) != null) { + log.info("line:{}", tmp); + if (tmp.endsWith(";")) { + sb.append(tmp); + sqlList.add(sb.toString()); + sb.delete(0, sb.length()); + } else { + sb.append(tmp); + } + } + if (!"".endsWith(sb.toString().trim())) { + sqlList.add(sb.toString()); + } + } + return sqlList; + } +} diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemDeptMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemDeptMapper.xml new file mode 100644 index 00000000..50704ee3 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemDeptMapper.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + update system_dept + set sub_count = #{count} + where dept_id = #{id} + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemDictDetailMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemDictDetailMapper.xml new file mode 100644 index 00000000..c920c767 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemDictDetailMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + delete from system_dict_detail + where dict_id in + + #{item} + + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemDictMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemDictMapper.xml new file mode 100644 index 00000000..5f99a547 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemDictMapper.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemJobMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemJobMapper.xml new file mode 100644 index 00000000..304c590d --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemJobMapper.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemLocalStorageMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemLocalStorageMapper.xml new file mode 100644 index 00000000..885ff06e --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemLocalStorageMapper.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemMenuMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemMenuMapper.xml new file mode 100644 index 00000000..5163f944 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemMenuMapper.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + update system_menu + set sub_count = #{count} + where menu_id = #{menuId} + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemOssStorageMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemOssStorageMapper.xml new file mode 100644 index 00000000..ecb4fc3b --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemOssStorageMapper.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemQuartzJobMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemQuartzJobMapper.xml new file mode 100644 index 00000000..d00d40cf --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemQuartzJobMapper.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemQuartzLogMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemQuartzLogMapper.xml new file mode 100644 index 00000000..c8f2585d --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemQuartzLogMapper.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemRoleDeptMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemRoleDeptMapper.xml new file mode 100644 index 00000000..e934d5a6 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemRoleDeptMapper.xml @@ -0,0 +1,19 @@ + + + + + insert into system_roles_depts (role_id, dept_id) + values + + #{roleId}, #{item.id} + + + + + delete from system_roles_depts + where role_id in + + #{id} + + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemRoleMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemRoleMapper.xml new file mode 100644 index 00000000..de0a83eb --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemRoleMapper.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemRoleMenuMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemRoleMenuMapper.xml new file mode 100644 index 00000000..62357dde --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemRoleMenuMapper.xml @@ -0,0 +1,23 @@ + + + + + insert into system_roles_menus (role_id, menu_id) + values + + #{roleId}, #{item.id} + + + + delete from system_roles_menus + where role_id in + + #{id} + + + + delete + from system_roles_menus + where role_id = #{roleId} + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemUserJobMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemUserJobMapper.xml new file mode 100644 index 00000000..34e9a028 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemUserJobMapper.xml @@ -0,0 +1,18 @@ + + + + + insert into system_users_jobs (user_id, job_id) + values + + #{userId}, #{item.id} + + + + delete from system_users_jobs + where user_id in + + #{id} + + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemUserMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemUserMapper.xml new file mode 100644 index 00000000..7d9ecc85 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemUserMapper.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + update system_user + set password = #{password}, + pwd_reset_time = NOW() + where username = #{username} + + + update system_user + set email = #{email} + where username = #{username} + + + + + + + + + update system_user set password = #{password} + where user_id in + + #{id} + + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemUserRoleMapper.xml b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemUserRoleMapper.xml new file mode 100644 index 00000000..49d524b5 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/mapper/SystemUserRoleMapper.xml @@ -0,0 +1,19 @@ + + + + + insert into system_users_roles (user_id, role_id) + values + + #{userId}, #{item.id} + + + + + delete from system_users_roles + where user_id in + + #{id} + + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/template/SystemEmailCaptchaTemplate.ftl b/cutejava-springboot3/cutejava-module-system/src/main/resources/template/SystemEmailCaptchaTemplate.ftl new file mode 100644 index 00000000..b326253a --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/template/SystemEmailCaptchaTemplate.ftl @@ -0,0 +1,50 @@ + + + + + + + +
+
+

尊敬的用户, 您好:

+

您正在申请邮箱验证,您的验证码为:

+

${code}

+
+
+
+ Copyright ©${.now?string("yyyy")} CuteJava + 后台管理系统 All Rights Reserved. +
+ +
+
+ + diff --git a/cutejava-springboot3/cutejava-module-system/src/main/resources/template/SystemQuartzJobTaskAlarmTemplate.ftl b/cutejava-springboot3/cutejava-module-system/src/main/resources/template/SystemQuartzJobTaskAlarmTemplate.ftl new file mode 100644 index 00000000..2a83165c --- /dev/null +++ b/cutejava-springboot3/cutejava-module-system/src/main/resources/template/SystemQuartzJobTaskAlarmTemplate.ftl @@ -0,0 +1,71 @@ + + + + + + + +
+
+

任务信息:

+ + + + + + + + + + + + + + + + + +
任务名称Bean名称执行方法参数内容Cron表达式描述内容
${task.jobName}${task.beanName}${task.methodName}${(task.params)!""}${task.cronExpression}${(task.description)!""}
+
+
+

异常信息:

+
+                ${msg}
+            
+
+
+
+
+ Copyright ©${.now?string("yyyy")} CuteJava + 后台管理系统 All Rights Reserved. +
+ +
+ + + diff --git a/cutejava-springboot3/cutejava-module-task/pom.xml b/cutejava-springboot3/cutejava-module-task/pom.xml new file mode 100644 index 00000000..63c2b15f --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/pom.xml @@ -0,0 +1,21 @@ + + + + cutejava + cn.odboy + 1.4.1 + + 4.0.0 + + cutejava-module-task + 任务模块 + + + + cn.odboy + cutejava-module-system + 1.4.1 + + + diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/constant/TaskChangeTypeEnum.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/constant/TaskChangeTypeEnum.java new file mode 100644 index 00000000..cb20c427 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/constant/TaskChangeTypeEnum.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 任务变更类型(固定值) + * + * @author odboy + * @date 2025-09-26 + */ +@Getter +@AllArgsConstructor +public enum TaskChangeTypeEnum { + AppApply("AppApply", "申请应用"), + AppContainerRestart("AppContainerRestart", "容器应用重启"), + AppContainerDeploy("AppContainerDeploy", "容器应用部署"), + AppOffline("AppOffline", "应用下线"), + AppChangeReplicas("AppChangeReplicas", "应用扩缩容"), + AppUpgrade("AppUpgrade", "应用升配"), + AppDowngrade("AppDowngrade", "应用降配"), + AppHostRestart("AppHostRestart", "主机应用重启"), + AppHostDeploy("AppHostDeploy", "主机应用部署"), + AppContainerRollback("AppContainerRollback", "容器应用回滚"), + AppHostRollback("AppHostRollback", "主机应用回滚"), + ResourceApply("ResourceApply", "申请资源"), + ResourceOffline("ResourceOffline", "资源下线"), + DataChange("DataChange", "数据变更"), + HostnameApply("HostnameApply", "申请域名"), + HostnameChange("HostnameChange", "域名变更"); + private final String code; + private final String name; +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/constant/TaskJobKeys.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/constant/TaskJobKeys.java new file mode 100644 index 00000000..783c7597 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/constant/TaskJobKeys.java @@ -0,0 +1,16 @@ +package cn.odboy.task.constant; + +/** + * 任务共用Keys + * + * @author odboy + * @date 2025-10-25 + */ +public interface TaskJobKeys { + String ID = "id"; + String CONTEXT_NAME = "contextName"; + String LANGUAGE = "language"; + String ENV_ALIAS = "envAlias"; + String CHANGE_TYPE = "changeType"; + String RETRY_NODE_CODE = "retryNodeCode"; +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/constant/TaskStatusEnum.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/constant/TaskStatusEnum.java new file mode 100644 index 00000000..6f09f6c4 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/constant/TaskStatusEnum.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 任务状态(固定值) + * + * @author odboy + * @date 2025-09-26 + */ +@Getter +@AllArgsConstructor +public enum TaskStatusEnum { + Running("running", "运行中"), + Success("success", "执行成功"), + Fail("fail", "执行失败"); + private final String code; + private final String name; + + public static String getDesc(String code) { + for (TaskStatusEnum taskStatusEnum : TaskStatusEnum.values()) { + if (taskStatusEnum.code.equals(code)) { + return taskStatusEnum.getName(); + } + } + return code; + } +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/controller/TaskTestsController.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/controller/TaskTestsController.java new file mode 100644 index 00000000..8a43cfdb --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/controller/TaskTestsController.java @@ -0,0 +1,78 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.controller; + +import cn.hutool.core.thread.ThreadUtil; +import cn.odboy.annotation.AnonymousAccess; +import cn.odboy.task.constant.TaskChangeTypeEnum; +import cn.odboy.task.core.TaskManage; +import cn.odboy.task.dal.dataobject.TaskInstanceInfoTb; +import cn.odboy.task.dal.model.TaskInstanceInfoVo; +import com.alibaba.fastjson2.JSON; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/api/task") +public class TaskTestsController { + @Autowired + private TaskManage taskManage; + + @AnonymousAccess + @GetMapping(value = "/testCreate") + public ResponseEntity testCreate() { + TaskInstanceInfoTb instanceInfo = taskManage.createJob("cutejava", TaskChangeTypeEnum.AppContainerDeploy, "java", "daily", "cutejava", "功能测试", null); + log.info("任务创建成功,实例为:{}", JSON.toJSONString(instanceInfo)); + return ResponseEntity.ok(instanceInfo); + } + + @AnonymousAccess + @GetMapping(value = "/testCreateAfterStop") + public ResponseEntity testCreateAfterStop() { + TaskInstanceInfoTb instanceInfo = taskManage.createJob("cutejava", TaskChangeTypeEnum.AppContainerDeploy, "java", "daily", "cutejava", "功能测试", null); + log.info("任务创建成功,实例为:{}", JSON.toJSONString(instanceInfo)); + ThreadUtil.execAsync(() -> { + ThreadUtil.safeSleep(5000); + taskManage.stopJob(instanceInfo.getId()); + }); + return ResponseEntity.ok(null); + } + + @AnonymousAccess + @GetMapping(value = "/testRetry") + public ResponseEntity testRetry(@RequestParam Long instanceId, @RequestParam String retryNodeCode) { + TaskInstanceInfoTb instanceInfo = taskManage.retryJob(instanceId, retryNodeCode); + log.info("任务重试成功,实例为:{}", JSON.toJSONString(instanceInfo)); + return ResponseEntity.ok(null); + } + + @AnonymousAccess + @GetMapping(value = "/last") + public ResponseEntity testLast() { + String contextName = "cutejava"; + String language = "java"; + String changeType = TaskChangeTypeEnum.AppContainerDeploy.getCode(); + String envAlias = "daily"; + TaskInstanceInfoVo instanceInfo = taskManage.getLastInfo(contextName, language, envAlias, changeType); + return ResponseEntity.ok(instanceInfo); + } +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/TaskJobBean.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/TaskJobBean.java new file mode 100644 index 00000000..7e1175a9 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/TaskJobBean.java @@ -0,0 +1,196 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.task.core; + +import cn.hutool.core.util.StrUtil; +import cn.odboy.framework.context.CsSpringBeanHolder; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.exception.ServerException; +import cn.odboy.task.constant.TaskJobKeys; +import cn.odboy.task.dal.dataobject.TaskInstanceDetailTb; +import cn.odboy.task.dal.dataobject.TaskInstanceInfoTb; +import cn.odboy.task.dal.model.TaskTemplateNodeVo; +import cn.odboy.task.service.TaskInstanceDetailService; +import cn.odboy.task.service.TaskInstanceInfoService; +import cn.odboy.task.service.TaskInstanceStepDetailService; +import com.alibaba.fastjson2.JSON; +import lombok.extern.slf4j.Slf4j; +import org.quartz.InterruptableJob; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.springframework.scheduling.quartz.QuartzJobBean; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +public class TaskJobBean extends QuartzJobBean implements InterruptableJob { + private Thread workThread = null; + + private String getBeanAlias(String code) { + if (StrUtil.isBlank(code)) { + throw new ServerException("参数code必填"); + } + // node_init + String[] s = code.split("_"); + return Arrays.stream(s).map(StrUtil::upperFirst).collect(Collectors.joining()); + } + + @Override + public void executeInternal(JobExecutionContext context) { + this.workThread = Thread.currentThread(); + // ========================== 获取代理类 ========================== + TaskInstanceInfoService taskInstanceInfoService = CsSpringBeanHolder.getBean(TaskInstanceInfoService.class); + TaskInstanceDetailService taskInstanceDetailService = CsSpringBeanHolder.getBean(TaskInstanceDetailService.class); + // ========================== 获取参数 ========================== + JobDataMap dataMap = context.getMergedJobDataMap(); + long id = dataMap.getLong(TaskJobKeys.ID); + // ========================== 获取任务编排模板 ========================== + TaskInstanceInfoTb taskInstanceInfoVo = taskInstanceInfoService.getById(id); + String templateInfo = taskInstanceInfoVo.getTemplate(); + List taskTemplateNodeVos = JSON.parseArray(templateInfo, TaskTemplateNodeVo.class); + // ========================== 判断是否重试任务 ========================== + String retryNodeCode = dataMap.getString(TaskJobKeys.RETRY_NODE_CODE); + if (StrUtil.isBlank(retryNodeCode)) { + executeNormalTask(taskInstanceInfoService, taskInstanceDetailService, id, dataMap, taskTemplateNodeVos); + } else { + executeRetryTask(taskInstanceInfoService, taskInstanceDetailService, id, dataMap, taskTemplateNodeVos, retryNodeCode); + } + } + + /** + * 执行普通任务(非重试任务) + * + * @param taskInstanceInfoService 任务实例信息服务 + * @param taskInstanceDetailService 任务实例明细服务 + * @param id 任务实例ID + * @param dataMap 数据映射 + * @param taskTemplateNodeVos 模板节点列表 + */ + private void executeNormalTask(TaskInstanceInfoService taskInstanceInfoService, TaskInstanceDetailService taskInstanceDetailService, long id, JobDataMap dataMap, List taskTemplateNodeVos) { + // ========================== 初始化执行明细 ========================== + List taskInstanceDetails = taskTemplateNodeVos.stream().map(taskTemplateNodeVo -> buildTaskInstanceDetail(id, taskTemplateNodeVo)).collect(Collectors.toList()); + taskInstanceDetailService.saveBatch(taskInstanceDetails); + Map codeIdMap = taskInstanceDetails.stream().collect(Collectors.toMap(TaskInstanceDetailTb::getBizCode, TaskInstanceDetailTb::getId)); + // ========================== 顺序执行 ========================== + executeTaskSteps(taskInstanceInfoService, taskInstanceDetailService, id, dataMap, taskTemplateNodeVos, codeIdMap); + } + + /** + * 执行重试任务 + * + * @param taskInstanceInfoService 任务实例信息服务 + * @param taskInstanceDetailService 任务实例明细服务 + * @param id 任务实例ID + * @param dataMap 数据映射 + * @param taskTemplateNodeVos 模板节点列表 + * @param retryNodeCode 重试节点编码 + */ + private void executeRetryTask(TaskInstanceInfoService taskInstanceInfoService, TaskInstanceDetailService taskInstanceDetailService, long id, JobDataMap dataMap, List taskTemplateNodeVos, String retryNodeCode) { + boolean isFound = false; + // ========================== 初始化执行明细 ========================== + List taskInstanceDetails = new ArrayList<>(); + List taskTemplateNodeRetryList = new ArrayList<>(); + for (TaskTemplateNodeVo taskTemplateNodeVo : taskTemplateNodeVos) { + if (isFound) { + taskInstanceDetails.add(buildTaskInstanceDetail(id, taskTemplateNodeVo)); + taskTemplateNodeRetryList.add(taskTemplateNodeVo); + continue; + } + if (taskTemplateNodeVo.getCode().equals(retryNodeCode)) { + if (!taskTemplateNodeVo.getRetry()) { + throw new BadRequestException("节点 '" + taskTemplateNodeVo.getName() + "' 不支持重试"); + } + taskInstanceDetails.add(buildTaskInstanceDetail(id, taskTemplateNodeVo)); + taskTemplateNodeRetryList.add(taskTemplateNodeVo); + isFound = true; + } + } + TaskInstanceStepDetailService taskInstanceStepDetailService = CsSpringBeanHolder.getBean(TaskInstanceStepDetailService.class); + List bizCodeList = taskInstanceDetails.stream().map(TaskInstanceDetailTb::getBizCode).distinct().collect(Collectors.toList()); + List taskInstanceDetailTbs = taskInstanceDetailService.queryByInstanceIdAndBizCodeList(id, bizCodeList); + List taskInstanceDetailIds = taskInstanceDetailTbs.stream().map(TaskInstanceDetailTb::getId).collect(Collectors.toList()); + // 根据ID删除明细 + taskInstanceDetailService.removeByIds(taskInstanceDetailIds); + // 根据明细删除步骤 + taskInstanceStepDetailService.removeByInstanceDetailIds(taskInstanceDetailIds); + taskInstanceDetailService.saveBatch(taskInstanceDetails); + Map codeIdMap = taskInstanceDetails.stream().collect(Collectors.toMap(TaskInstanceDetailTb::getBizCode, TaskInstanceDetailTb::getId)); + // ========================== 顺序执行 ========================== + executeTaskSteps(taskInstanceInfoService, taskInstanceDetailService, id, dataMap, taskTemplateNodeRetryList, codeIdMap); + } + + /** + * 构建任务实例明细对象 + * + * @param id 实例ID + * @param taskTemplateNodeVo 模板节点 + * @return 任务实例明细对象 + */ + private TaskInstanceDetailTb buildTaskInstanceDetail(long id, TaskTemplateNodeVo taskTemplateNodeVo) { + TaskInstanceDetailTb taskInstanceDetail = new TaskInstanceDetailTb(); + taskInstanceDetail.setInstanceId(id); + taskInstanceDetail.setFinishTime(null); + taskInstanceDetail.setBizCode(taskTemplateNodeVo.getCode()); + taskInstanceDetail.setBizName(taskTemplateNodeVo.getName()); + taskInstanceDetail.setExecuteInfo("未开始"); + taskInstanceDetail.setExecuteStatus("pending"); + return taskInstanceDetail; + } + + /** + * 执行任务步骤 + * + * @param taskInstanceInfoService 任务实例信息服务 + * @param taskInstanceDetailService 任务实例明细服务 + * @param id 实例ID + * @param dataMap 数据映射 + * @param taskTemplateNodes 需要执行的任务节点列表 + * @param codeIdMap 节点编码与明细ID映射 + */ + private void executeTaskSteps(TaskInstanceInfoService taskInstanceInfoService, TaskInstanceDetailService taskInstanceDetailService, Long id, JobDataMap dataMap, List taskTemplateNodes, Map codeIdMap) { + try { + for (TaskTemplateNodeVo taskTemplateNodeVo : taskTemplateNodes) { + String code = taskTemplateNodeVo.getCode(); + try { + TaskStepExecutor executor = CsSpringBeanHolder.getBean("taskStep" + getBeanAlias(code)); + taskInstanceDetailService.fastStart(id, code, dataMap); + executor.execute(codeIdMap.getOrDefault(code, null), dataMap, taskTemplateNodeVo); + taskInstanceDetailService.fastSuccessWithInfo(id, code, null); + } catch (Exception e) { + log.error("任务节点执行失败", e); + taskInstanceDetailService.fastFailWithInfo(id, code, e.getMessage()); + throw new ServerException(e); + } + } + taskInstanceInfoService.fastSuccessWithData(id, dataMap); + } catch (Exception e) { + log.error("任务执行失败", e); + taskInstanceInfoService.fastFailWithMessageData(id, e.getMessage(), dataMap); + } + } + + @Override + public void interrupt() { + if (workThread != null) { + workThread.stop(); + } + } +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/TaskManage.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/TaskManage.java new file mode 100644 index 00000000..10935257 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/TaskManage.java @@ -0,0 +1,255 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.core; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.framework.context.CsSpringBeanHolder; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.task.constant.TaskChangeTypeEnum; +import cn.odboy.task.constant.TaskJobKeys; +import cn.odboy.task.constant.TaskStatusEnum; +import cn.odboy.task.dal.dataobject.TaskInstanceDetailTb; +import cn.odboy.task.dal.dataobject.TaskInstanceInfoTb; +import cn.odboy.task.dal.model.TaskInstanceInfoVo; +import cn.odboy.task.dal.model.TaskInstanceNodeVo; +import cn.odboy.task.dal.model.TaskTemplateInfoVo; +import cn.odboy.task.dal.model.TaskTemplateNodeVo; +import cn.odboy.task.service.TaskInstanceDetailService; +import cn.odboy.task.service.TaskInstanceInfoService; +import cn.odboy.task.service.TaskTemplateInfoService; +import cn.odboy.util.CsDateUtil; +import com.alibaba.fastjson2.JSON; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.quartz.*; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class TaskManage { + private final Scheduler scheduler; + private final TaskTemplateInfoService taskTemplateInfoService; + private final TaskInstanceInfoService taskInstanceInfoService; + private final TaskInstanceDetailService taskInstanceDetailService; + + /** + * 创建任务单(注:同一应用同一变更类型无法并发多实例) + * + * @param contextName 上下文名称,这里特指应用名 + * @param changeTypeEnum 变更类型 + * @param language 开发语言、资源版本 + * @param envAlias 环境别名 + * @param source 来源 + * @param reason 变更原因 + * @param dataMap 任务参数 + */ + @Transactional(rollbackFor = Exception.class) + public TaskInstanceInfoTb createJob(String contextName, TaskChangeTypeEnum changeTypeEnum, String language, String envAlias, String source, String reason, JobDataMap dataMap) { + if (StrUtil.isBlank(contextName)) { + throw new BadRequestException("参数contextName必填"); + } + if (changeTypeEnum == null) { + throw new BadRequestException("参数changeTypeEnum必填"); + } + if (StrUtil.isBlank(language)) { + throw new BadRequestException("参数language必填"); + } + if (StrUtil.isBlank(envAlias)) { + throw new BadRequestException("参数envAlias必填"); + } + if (StrUtil.isBlank(source)) { + throw new BadRequestException("参数source必填"); + } + if (dataMap == null) { + dataMap = new JobDataMap(); + } + // ========================== 获取任务编排模板 ========================== + TaskTemplateInfoVo taskInstanceInfoVo = taskTemplateInfoService.getTemplateInfoByECL(envAlias, contextName, language, changeTypeEnum.getCode()); + if (taskInstanceInfoVo == null) { + throw new BadRequestException("没有查询到任务编排模板"); + } + String templateInfo = taskInstanceInfoVo.getTemplateInfo(); + List taskTemplateNodeVos; + try { + taskTemplateNodeVos = JSON.parseArray(templateInfo, TaskTemplateNodeVo.class); + } catch (Exception e) { + log.error("任务编排模板解析失败", e); + throw new BadRequestException("任务编排模板解析失败"); + } + if (taskTemplateNodeVos == null || taskTemplateNodeVos.isEmpty()) { + throw new BadRequestException("没有查询到任务编排明细"); + } + // ========================== 创建任务 ========================== + TaskManage taskManage = CsSpringBeanHolder.getBean(TaskManage.class); + TaskInstanceInfoTb newInstance = taskManage.saveTaskInstanceInfoTb(contextName, changeTypeEnum, language, envAlias, source, reason, dataMap, templateInfo); + dataMap.put(TaskJobKeys.ID, newInstance.getId()); + // 应用名、资源类型 + dataMap.put(TaskJobKeys.CONTEXT_NAME, contextName); + // 开发语言、资源版本 + dataMap.put(TaskJobKeys.LANGUAGE, language); + dataMap.put(TaskJobKeys.ENV_ALIAS, envAlias); + // 变更类型 + dataMap.put(TaskJobKeys.CHANGE_TYPE, changeTypeEnum.getCode()); + buildAndSchedule(changeTypeEnum.getCode(), contextName, dataMap); + return newInstance; + } + + @Transactional(rollbackFor = Exception.class) + public TaskInstanceInfoTb saveTaskInstanceInfoTb(String contextName, TaskChangeTypeEnum changeTypeEnum, String language, String envAlias, String source, String reason, JobDataMap dataMap, String templateInfo) { + TaskInstanceInfoTb newInstance = new TaskInstanceInfoTb(); + newInstance.setContextName(contextName); + newInstance.setLanguage(language); + newInstance.setChangeType(changeTypeEnum.getCode()); + newInstance.setEnvAlias(envAlias); + newInstance.setStatus(TaskStatusEnum.Running.getCode()); + newInstance.setFinishTime(null); + newInstance.setSource(source); + newInstance.setReason(reason); + newInstance.setTemplate(templateInfo); + newInstance.setJobData(JSON.toJSONString(dataMap)); + taskInstanceInfoService.save(newInstance); + return newInstance; + } + + private void buildAndSchedule(String changeTypeEnum, String contextName, JobDataMap dataMap) { + // ========================== 执行任务 ========================== + JobKey jobKey = JobKey.jobKey(changeTypeEnum, contextName); + TriggerKey triggerKey = TriggerKey.triggerKey(changeTypeEnum, contextName); + JobDetail jobDetail = JobBuilder.newJob(TaskJobBean.class).withIdentity(jobKey).usingJobData(dataMap).build(); + Trigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).startNow().build(); + try { + scheduler.scheduleJob(jobDetail, trigger); + } catch (ObjectAlreadyExistsException e) { + log.error("任务jobKey={},triggerKey={}任务已存在,跳过加载", jobKey.getName(), triggerKey.getName(), e); + throw new BadRequestException("任务已存在,跳过加载"); + } catch (SchedulerException e) { + log.error("任务jobKey={},triggerKey={}执行失败", jobKey.getName(), triggerKey.getName(), e); + throw new BadRequestException(e); + } + } + + @Transactional(rollbackFor = Exception.class) + public void stopJob(Long instanceId) { + TaskInstanceInfoTb taskInstanceInfoTb = taskInstanceInfoService.getRunningById(instanceId); + + String changeType = taskInstanceInfoTb.getChangeType(); + String contextName = taskInstanceInfoTb.getContextName(); + + taskInstanceInfoTb.setStatus(TaskStatusEnum.Fail.getCode()); + taskInstanceInfoTb.setErrorMessage("任务被中断"); + taskInstanceInfoTb.setFinishTime(new Date()); + taskInstanceInfoService.updateById(taskInstanceInfoTb); + + try { + JobKey jobKey = JobKey.jobKey(changeType, contextName); + TriggerKey triggerKey = TriggerKey.triggerKey(changeType, contextName); + // 停止触发器 + scheduler.pauseTrigger(triggerKey); + // 移除触发器 + scheduler.unscheduleJob(triggerKey); + // 删除并中断任务 + scheduler.deleteJob(jobKey); + scheduler.interrupt(jobKey); + } catch (SchedulerException e) { + log.error("任务停止失败", e); + throw new RuntimeException(e); + } + } + + @Transactional(rollbackFor = Exception.class) + public TaskInstanceInfoTb retryJob(Long id, String retryNodeCode) { + // ========================== 查询任务 ========================== + TaskInstanceInfoTb taskInstanceInfoTb = taskInstanceInfoService.getById(id); + if (taskInstanceInfoTb == null) { + throw new BadRequestException("任务不存在"); + } + // ========================== 失败的任务才能重试 ========================== + if (TaskStatusEnum.Success.getCode().equals(taskInstanceInfoTb.getStatus())) { + throw new BadRequestException("任务已执行完毕,无法重试"); + } + // 参数重放 + JobDataMap dataMap = JSON.parseObject(taskInstanceInfoTb.getJobData(), JobDataMap.class); + if (dataMap == null) { + dataMap = new JobDataMap(); + } + taskInstanceInfoTb.setStatus(TaskStatusEnum.Running.getCode()); + taskInstanceInfoTb.setFinishTime(null); + taskInstanceInfoService.updateById(taskInstanceInfoTb); + // ========================== 执行任务 ========================== + dataMap.put(TaskJobKeys.RETRY_NODE_CODE, retryNodeCode); + String changeType = taskInstanceInfoTb.getChangeType(); + String contextName = taskInstanceInfoTb.getContextName(); + buildAndSchedule(changeType, contextName, dataMap); + return taskInstanceInfoTb; + } + + public TaskInstanceInfoVo getLastInfo(String contextName, String language, String envAlias, String changeType) { + TaskInstanceInfoVo record = new TaskInstanceInfoVo(); + + TaskInstanceInfoTb historyInstance = taskInstanceInfoService.getLastHistoryInstance(contextName, language, envAlias, changeType); + if (historyInstance == null) { + // 仅返回模板 + TaskTemplateInfoVo templateInfo = taskTemplateInfoService.getTemplateInfoByECL(envAlias, contextName, language, changeType); + if (templateInfo == null) { + throw new BadRequestException("没有查询到任务编排模板"); + } + record.setTemplate(templateInfo.getTemplateInfo()); + return record; + } + + record = BeanUtil.copyProperties(historyInstance, TaskInstanceInfoVo.class); + record.setHistory(buildNodeList(record)); + + TaskInstanceInfoTb runningInstance = taskInstanceInfoService.getLastRunningInstance(contextName, language, envAlias, changeType); + if (runningInstance != null) { + TaskInstanceInfoVo taskInstanceInfoVo = BeanUtil.copyProperties(runningInstance, TaskInstanceInfoVo.class); + record.setCurrent(buildNodeList(taskInstanceInfoVo)); + } + return record; + } + + private List buildNodeList(TaskInstanceInfoVo record) { + List records = new ArrayList<>(); + + // 节点明细 + List taskInstanceDetails = taskInstanceDetailService.queryByInstanceId(record.getId()); + for (TaskInstanceDetailTb taskInstanceDetail : taskInstanceDetails) { + TaskInstanceNodeVo instanceNodeVo = new TaskInstanceNodeVo(); + instanceNodeVo.setCode(taskInstanceDetail.getBizCode()); + instanceNodeVo.setName(taskInstanceDetail.getBizName()); + instanceNodeVo.setStartTime(taskInstanceDetail.getStartTime()); + instanceNodeVo.setFinishTime(taskInstanceDetail.getFinishTime()); + if (taskInstanceDetail.getFinishTime() == null) { + instanceNodeVo.setDurationDesc(CsDateUtil.formatSecondsDuration(taskInstanceDetail.getStartTime(), new Date())); + } else { + instanceNodeVo.setDurationDesc(CsDateUtil.formatSecondsDuration(taskInstanceDetail.getStartTime(), taskInstanceDetail.getFinishTime())); + } + instanceNodeVo.setRunningDesc(taskInstanceDetail.getExecuteInfo()); + instanceNodeVo.setStatus(taskInstanceDetail.getExecuteStatus()); + instanceNodeVo.setStatusDesc(TaskStatusEnum.getDesc(taskInstanceDetail.getExecuteStatus())); + records.add(instanceNodeVo); + } + + return records; + } +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/TaskStepExecutor.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/TaskStepExecutor.java new file mode 100644 index 00000000..3d53c8e4 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/TaskStepExecutor.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.core; + +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.task.dal.model.TaskTemplateNodeVo; +import org.quartz.JobDataMap; + +public interface TaskStepExecutor { + /** + * @param instanceDetailId 任务实例明细id + * @param jobDataMap 执行参数 + * @param taskTemplateNode 任务节点信息 + */ + void execute(Long instanceDetailId, JobDataMap jobDataMap, TaskTemplateNodeVo taskTemplateNode) throws BadRequestException; +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeBuildJava.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeBuildJava.java new file mode 100644 index 00000000..58df89ed --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeBuildJava.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.core.impl; + +import cn.hutool.core.thread.ThreadUtil; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.task.core.TaskStepExecutor; +import cn.odboy.task.dal.model.TaskTemplateNodeVo; +import lombok.extern.slf4j.Slf4j; +import org.quartz.JobDataMap; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class TaskStepNodeBuildJava implements TaskStepExecutor { + + @Override + public void execute(Long instanceDetailId, JobDataMap jobDataMap, TaskTemplateNodeVo taskTemplateNode) throws BadRequestException { + ThreadUtil.sleep(2000); + } +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeDeployJava.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeDeployJava.java new file mode 100644 index 00000000..fb97818b --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeDeployJava.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.core.impl; + +import cn.hutool.core.thread.ThreadUtil; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.task.core.TaskStepExecutor; +import cn.odboy.task.dal.model.TaskTemplateNodeVo; +import lombok.extern.slf4j.Slf4j; +import org.quartz.JobDataMap; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class TaskStepNodeDeployJava implements TaskStepExecutor { + + @Override + public void execute(Long instanceDetailId, JobDataMap jobDataMap, TaskTemplateNodeVo taskTemplateNode) throws BadRequestException { + ThreadUtil.sleep(5000); + } +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeInit.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeInit.java new file mode 100644 index 00000000..ef9c0936 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeInit.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.core.impl; + +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.task.core.TaskStepExecutor; +import cn.odboy.task.dal.model.TaskTemplateNodeVo; +import cn.odboy.task.service.TaskInstanceStepDetailService; +import lombok.extern.slf4j.Slf4j; +import org.quartz.JobDataMap; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class TaskStepNodeInit implements TaskStepExecutor { + @Autowired + private TaskInstanceStepDetailService stepDetailService; + + @Override + public void execute(Long instanceDetailId, JobDataMap jobDataMap, TaskTemplateNodeVo taskTemplateNode) throws BadRequestException { + stepDetailService.success(instanceDetailId, "开始初始化"); + stepDetailService.success(instanceDetailId, "初始化完毕后,放入了一些参数"); + jobDataMap.put("ExcuseMe", "Hello World!"); + } +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeMergeBranch.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeMergeBranch.java new file mode 100644 index 00000000..1cd1c2c8 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/core/impl/TaskStepNodeMergeBranch.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.core.impl; + +import cn.hutool.core.thread.ThreadUtil; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.task.core.TaskStepExecutor; +import cn.odboy.task.dal.model.TaskTemplateNodeVo; +import cn.odboy.task.service.TaskInstanceStepDetailService; +import lombok.extern.slf4j.Slf4j; +import org.quartz.JobDataMap; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class TaskStepNodeMergeBranch implements TaskStepExecutor { + @Autowired + private TaskInstanceStepDetailService stepDetailService; + + @Override + public void execute(Long instanceDetailId, JobDataMap jobDataMap, TaskTemplateNodeVo taskTemplateNode) throws BadRequestException { + ThreadUtil.sleep(3000); + stepDetailService.fail(instanceDetailId, "执行失败测试"); +// stepDetailService.success(instanceDetailId, "执行成功"); + } +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskInstanceDetailTb.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskInstanceDetailTb.java new file mode 100644 index 00000000..62f83011 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskInstanceDetailTb.java @@ -0,0 +1,113 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.dal.dataobject; + +import cn.odboy.base.CsObject; +import com.alibaba.fastjson2.annotation.JSONField; +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.util.Date; + +/** + *

+ * 任务实例明细 + *

+ * + * @author codegen + * @since 2025-09-26 + */ +@Getter +@Setter +@ToString +@TableName("task_instance_detail") +@ApiModel(value = "TaskInstanceDetailTb对象", description = "任务实例明细") +public class TaskInstanceDetailTb extends CsObject { + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true) + private Date createTime; + + /** + * id + */ + @ApiModelProperty("id") + @TableId(value = "id", type = IdType.ASSIGN_ID) + @JsonSerialize(using = ToStringSerializer.class) + @JSONField(format = "string") + private Long id; + + /** + * 任务实例id + */ + @TableField("instance_id") + @ApiModelProperty("任务实例id") + private Long instanceId; + + /** + * 开始时间 + */ + @ApiModelProperty("开始时间") + @TableField("start_time") + private Date startTime; + + /** + * 结束时间 + */ + @ApiModelProperty("结束时间") + @TableField("finish_time") + private Date finishTime; + + /** + * 业务编码 + */ + @TableField("biz_code") + @ApiModelProperty("业务编码") + private String bizCode; + + /** + * 业务名称(步骤) + */ + @TableField("biz_name") + @ApiModelProperty("业务名称(步骤)") + private String bizName; + + /** + * 执行参数 + */ + @ApiModelProperty("执行参数") + @TableField("execute_params") + private String executeParams; + + /** + * 执行信息 + */ + @ApiModelProperty("执行信息") + @TableField("execute_info") + private String executeInfo; + + /** + * 执行状态(running进行中 success成功 fail失败) + */ + @TableField("execute_status") + @ApiModelProperty("执行状态(running进行中 success成功 fail失败)") + private String executeStatus; +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskInstanceInfoTb.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskInstanceInfoTb.java new file mode 100644 index 00000000..737026c7 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskInstanceInfoTb.java @@ -0,0 +1,144 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.dal.dataobject; + +import cn.odboy.base.CsObject; +import com.alibaba.fastjson2.annotation.JSONField; +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.springframework.data.annotation.CreatedBy; + +import java.util.Date; + +/** + *

+ * 任务实例 + *

+ * + * @author codegen + * @since 2025-09-26 + */ +@Getter +@Setter +@ToString +@TableName("task_instance_info") +@ApiModel(value = "TaskInstanceInfoTb对象", description = "任务实例") +public class TaskInstanceInfoTb extends CsObject { + @CreatedBy + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建人", hidden = true) + private String createBy; + + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true) + private Date createTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @ApiModelProperty(value = "更新时间: yyyy-MM-dd HH:mm:ss", hidden = true) + private Date updateTime; + + /** + * id, @JsonSerialize和@JSONField,用于处理大数精度丢失问题 + */ + @ApiModelProperty("id") + @TableId(value = "id", type = IdType.ASSIGN_ID) + @JsonSerialize(using = ToStringSerializer.class) + @JSONField(format = "string") + private Long id; + + /** + * 名称 + */ + @ApiModelProperty("名称") + @TableField("context_name") + private String contextName; + + /** + * 语言 + */ + @ApiModelProperty("语言") + @TableField("`language`") + private String language; + + /** + * 变更类型 + */ + @ApiModelProperty("变更类型") + @TableField("change_type") + private String changeType; + + /** + * 环境别名 + */ + @TableField("env_alias") + @ApiModelProperty("环境别名") + private String envAlias; + + /** + * 状态(running进行中 success成功 fail失败) + */ + @TableField("`status`") + @ApiModelProperty("状态(running进行中 success成功 fail失败)") + private String status; + + /** + * 完成时间 + */ + @ApiModelProperty("完成时间") + @TableField("finish_time") + private Date finishTime; + + /** + * 来源 + */ + @ApiModelProperty("来源") + @TableField("`source`") + private String source; + + /** + * 变更原因 + */ + @TableField("reason") + @ApiModelProperty("变更原因") + private String reason; + + /** + * 任务模板 + */ + @TableField("template") + @ApiModelProperty("template") + private String template; + + /** + * QuartzJob参数 + */ + @TableField("job_data") + @ApiModelProperty("QuartzJob参数") + private String jobData; + + /** + * 异常信息 + */ + @TableField("error_message") + @ApiModelProperty("异常信息") + private String errorMessage; +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskInstanceStepDetailTb.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskInstanceStepDetailTb.java new file mode 100644 index 00000000..92d8e7b6 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskInstanceStepDetailTb.java @@ -0,0 +1,79 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.dal.dataobject; + +import cn.odboy.base.CsObject; +import com.alibaba.fastjson2.annotation.JSONField; +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.util.Date; + + +/** + *

+ * 任务实例步骤明细 + *

+ * + * @author codegen + * @since 2025-09-28 + */ +@Getter +@Setter +@ToString +@TableName("task_instance_step_detail") +@ApiModel(value = "TaskInstanceStepDetailTb对象", description = "") +public class TaskInstanceStepDetailTb extends CsObject { + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true) + private Date createTime; + + /** + * id + */ + @ApiModelProperty("id") + @TableId(value = "id", type = IdType.ASSIGN_ID) + @JsonSerialize(using = ToStringSerializer.class) + @JSONField(format = "string") + private Long id; + + /** + * task_instance_detail表id + */ + @TableField("instance_detail_id") + @ApiModelProperty("task_instance_detail表id") + private Long instanceDetailId; + + /** + * 步骤描述 + */ + @TableField("step_desc") + @ApiModelProperty("步骤描述") + private String stepDesc; + + /** + * 状态(success成功 fail失败) + */ + @TableField("step_status") + @ApiModelProperty("状态(success成功 fail失败)") + private String stepStatus; +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskTemplateInfoTb.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskTemplateInfoTb.java new file mode 100644 index 00000000..98d40927 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/dataobject/TaskTemplateInfoTb.java @@ -0,0 +1,100 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.dal.dataobject; + +import cn.odboy.base.CsBaseUserTimeTb; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + + +/** + *

+ * 任务模板 + *

+ * + * @author codegen + * @since 2025-09-28 + */ +@Getter +@Setter +@ToString +@TableName("task_template_info") +@ApiModel(value = "TaskTemplateInfoTb对象", description = "任务模板") +public class TaskTemplateInfoTb extends CsBaseUserTimeTb { + + /** + * id + */ + @ApiModelProperty("id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 数据有效性 + */ + @TableField("available") + @ApiModelProperty("数据有效性") + private Boolean available; + + /** + * 变更类型 + */ + @ApiModelProperty("变更类型") + @TableField("change_type") + private String changeType; + + /** + * 流水线名称 + */ + @TableField("`name`") + @ApiModelProperty("流水线名称") + private String name; + + /** + * 流水线描述 + */ + @ApiModelProperty("流水线描述") + @TableField("`description`") + private String description; + + /** + * 流水线模板内容 + */ + @TableField("template") + @ApiModelProperty("流水线模板内容") + private String template; + + /** + * 环境别名 + */ + @TableField("env_alias") + @ApiModelProperty("环境别名") + private String envAlias; + + /** + * 应用为语言,资源为类型 + */ + @TableField("`language`") + @ApiModelProperty("应用为语言,资源为类型") + private String language; +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskInstanceInfoVo.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskInstanceInfoVo.java new file mode 100644 index 00000000..0bfdff33 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskInstanceInfoVo.java @@ -0,0 +1,26 @@ +package cn.odboy.task.dal.model; + +import cn.odboy.task.dal.dataobject.TaskInstanceInfoTb; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * 任务实例数据 + * + * @author odboy + * @date 2025-10-01 + */ +@Getter +@Setter +public class TaskInstanceInfoVo extends TaskInstanceInfoTb { + /** + * 进行中 + */ + private List current; + /** + * 历史已完成或异常 + */ + private List history; +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskInstanceNodeVo.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskInstanceNodeVo.java new file mode 100644 index 00000000..645f748d --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskInstanceNodeVo.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.task.dal.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +/** + * 任务节点数据 + * + * @author odboy + */ +@Getter +@Setter +public class TaskInstanceNodeVo extends CsObject { + /** + * 业务编码 + */ + protected String code; + /** + * 业务名称 + */ + protected String name; + /** + * 开始时间 + */ + protected Date startTime; + /** + * 完成时间 + */ + protected Date finishTime; + /** + * 耗时多久 + */ + protected String durationDesc; + /** + * 进行中描述 + */ + protected String runningDesc; + /** + * 节点状态编码 + */ + protected String status; + /** + * 节点状态描述 + */ + protected String statusDesc; +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskTemplateInfoVo.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskTemplateInfoVo.java new file mode 100644 index 00000000..fde41eb8 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskTemplateInfoVo.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.dal.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TaskTemplateInfoVo extends CsObject { + private String changeType; + private String name; + private String description; + private String envAlias; + private String language; + private String contextName; + private String templateInfo; +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskTemplateNodeOperateButtonVo.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskTemplateNodeOperateButtonVo.java new file mode 100644 index 00000000..10c3558c --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskTemplateNodeOperateButtonVo.java @@ -0,0 +1,50 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.task.dal.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.Map; + +/** + * 流水线节点控制按钮 + * + * @author odboy + */ +@Getter +@Setter +public class TaskTemplateNodeOperateButtonVo extends CsObject { + /** + * 按钮类型(service:调用某个服务并传递参数 link:带参跳转) + */ + private String type; + /** + * 按钮标题 + */ + private String title; + /** + * 按钮传递的参数 + */ + private String code; + /** + * 默认附加参数 + */ + private Map parameters = new HashMap<>(); +} \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskTemplateNodeVo.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskTemplateNodeVo.java new file mode 100644 index 00000000..167c94c0 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/model/TaskTemplateNodeVo.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.task.dal.model; + +import cn.odboy.base.CsObject; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 任务节点模板 + * + * @author odboy + */ +@Getter +@Setter +public class TaskTemplateNodeVo extends CsObject { + /** + * 业务编码 + */ + protected String code; + /** + * 业务类型(service:系统内置服务 rpc:远程调用) + */ + protected String type; + /** + * 业务名称 + */ + protected String name; + /** + * 是否可点击 + */ + protected Boolean click = false; + /** + * 是否可重试 + */ + protected Boolean retry = false; + /** + * 是否可点击:点击展示详情,详情内容类型 + */ + protected String detailType = ""; + /** + * 默认参数
execute: 执行调用 describe: 明细调用 + */ + protected Map parameters = new HashMap<>(); + /** + * 流水线节点控制按钮 + */ + protected List buttons = new ArrayList<>(); +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskInstanceDetailMapper.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskInstanceDetailMapper.java new file mode 100644 index 00000000..c79849e5 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskInstanceDetailMapper.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.dal.mysql; + +import cn.odboy.task.dal.dataobject.TaskInstanceDetailTb; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * 任务实例明细 Mapper 接口 + *

+ * + * @author codegen + * @since 2025-09-26 + */ +@Mapper +public interface TaskInstanceDetailMapper extends BaseMapper { + +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskInstanceInfoMapper.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskInstanceInfoMapper.java new file mode 100644 index 00000000..682b0b8c --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskInstanceInfoMapper.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.dal.mysql; + +import cn.odboy.task.dal.dataobject.TaskInstanceInfoTb; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * 任务实例 Mapper 接口 + *

+ * + * @author codegen + * @since 2025-09-26 + */ +@Mapper +public interface TaskInstanceInfoMapper extends BaseMapper { + +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskInstanceStepDetailMapper.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskInstanceStepDetailMapper.java new file mode 100644 index 00000000..67451484 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskInstanceStepDetailMapper.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.dal.mysql; + +import cn.odboy.task.dal.dataobject.TaskInstanceStepDetailTb; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * 任务实例步骤明细 Mapper 接口 + *

+ * + * @author codegen + * @since 2025-09-28 + */ +@Mapper +public interface TaskInstanceStepDetailMapper extends BaseMapper { + +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskTemplateInfoMapper.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskTemplateInfoMapper.java new file mode 100644 index 00000000..e646614e --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/dal/mysql/TaskTemplateInfoMapper.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.dal.mysql; + +import cn.odboy.task.dal.dataobject.TaskTemplateInfoTb; +import cn.odboy.task.dal.model.TaskTemplateInfoVo; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + *

+ * 任务模板 Mapper 接口 + *

+ * + * @author codegen + * @since 2025-09-28 + */ +@Mapper +public interface TaskTemplateInfoMapper extends BaseMapper { + /** + * @param envAlias 环境别称 + * @param contextName 应用名称、资源类型 + * @param language 语言、版本 + * @param changeType 变更类型 + * @return / + */ + TaskTemplateInfoVo selectTemplateInfoByECL( + @Param("envAlias") String envAlias, + @Param("contextName") String contextName, + @Param("language") String language, + @Param("changeType") String changeType + ); +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskInstanceDetailService.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskInstanceDetailService.java new file mode 100644 index 00000000..5e892de6 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskInstanceDetailService.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.service; + +import cn.odboy.task.dal.dataobject.TaskInstanceDetailTb; +import com.baomidou.mybatisplus.extension.service.IService; +import org.quartz.JobDataMap; + +import java.util.List; + +/** + *

+ * 任务实例明细 服务类 + *

+ * + * @author codegen + * @since 2025-09-26 + */ +public interface TaskInstanceDetailService extends IService { + void fastFailWithInfo(Long instanceId, String code, String message); + + void fastSuccess(Long instanceId, String code); + + void fastSuccessWithInfo(Long instanceId, String code, String executeInfo); + + void fastStart(Long instanceId, String code, JobDataMap dataMap); + + List queryByInstanceIdAndBizCodeList(Long instanceId, List bizCodes); + + List queryByInstanceId(Long instanceId); +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskInstanceInfoService.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskInstanceInfoService.java new file mode 100644 index 00000000..57c6d53c --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskInstanceInfoService.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.service; + +import cn.odboy.task.dal.dataobject.TaskInstanceInfoTb; +import com.baomidou.mybatisplus.extension.service.IService; +import org.quartz.JobDataMap; + +/** + *

+ * 任务实例 服务类 + *

+ * + * @author codegen + * @since 2025-09-26 + */ +public interface TaskInstanceInfoService extends IService { + TaskInstanceInfoTb getRunningById(Long instanceId); + + void fastFailWithMessage(Long id, String errorMessage); + + void fastFailWithMessageData(Long id, String errorMessage, JobDataMap dataMap); + + void fastSuccessWithData(Long id, JobDataMap dataMap); + + TaskInstanceInfoTb getLastRunningInstance(String contextName, String language, String envAlias, String changeType); + + TaskInstanceInfoTb getLastHistoryInstance(String contextName, String language, String envAlias, String changeType); +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskInstanceStepDetailService.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskInstanceStepDetailService.java new file mode 100644 index 00000000..105ab133 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskInstanceStepDetailService.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.service; + +import cn.odboy.task.dal.dataobject.TaskInstanceStepDetailTb; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + *

+ * 任务实例步骤明细 服务类 + *

+ * + * @author codegen + * @since 2025-09-28 + */ +public interface TaskInstanceStepDetailService extends IService { + /** + * 步骤执行成功 + * + * @param instanceDetailId 任务实例明细id + * @param stepDesc 步骤描述 + */ + void success(Long instanceDetailId, String stepDesc); + + /** + * 步骤执行失败 + * + * @param instanceDetailId 任务实例明细id + * @param stepDesc 步骤描述 + */ + void fail(Long instanceDetailId, String stepDesc); + + /** + * 根据明细ID删除步骤明细 + * + * @param instanceDetailIds 任务实例明细id集合 + */ + void removeByInstanceDetailIds(List instanceDetailIds); +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskTemplateInfoService.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskTemplateInfoService.java new file mode 100644 index 00000000..6277818a --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/TaskTemplateInfoService.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.service; + +import cn.odboy.task.dal.dataobject.TaskTemplateInfoTb; +import cn.odboy.task.dal.model.TaskTemplateInfoVo; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 任务模板 服务类 + *

+ * + * @author codegen + * @since 2025-09-28 + */ +public interface TaskTemplateInfoService extends IService { + TaskTemplateInfoVo getTemplateInfoByECL(String envAlias, String contextName, String language, String changeType); +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskInstanceDetailServiceImpl.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskInstanceDetailServiceImpl.java new file mode 100644 index 00000000..bd8cdb88 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskInstanceDetailServiceImpl.java @@ -0,0 +1,91 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.odboy.task.constant.TaskStatusEnum; +import cn.odboy.task.dal.dataobject.TaskInstanceDetailTb; +import cn.odboy.task.dal.mysql.TaskInstanceDetailMapper; +import cn.odboy.task.service.TaskInstanceDetailService; +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.quartz.JobDataMap; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; + +/** + *

+ * 任务实例明细 服务实现类 + *

+ * + * @author codegen + * @since 2025-09-26 + */ +@Service +public class TaskInstanceDetailServiceImpl extends ServiceImpl implements TaskInstanceDetailService { + + @Override + public void fastFailWithInfo(Long instanceId, String code, String executeInfo) { + TaskInstanceDetailTb updRecord = new TaskInstanceDetailTb(); + updRecord.setFinishTime(new Date()); + updRecord.setExecuteInfo(executeInfo); + updRecord.setExecuteStatus(TaskStatusEnum.Fail.getCode()); + lambdaUpdate().eq(TaskInstanceDetailTb::getInstanceId, instanceId).eq(TaskInstanceDetailTb::getBizCode, code).update(updRecord); + } + + @Override + public void fastSuccess(Long instanceId, String code) { + TaskInstanceDetailTb updRecord = new TaskInstanceDetailTb(); + updRecord.setFinishTime(new Date()); + updRecord.setExecuteInfo("执行成功"); + updRecord.setExecuteStatus(TaskStatusEnum.Success.getCode()); + lambdaUpdate().eq(TaskInstanceDetailTb::getInstanceId, instanceId).eq(TaskInstanceDetailTb::getBizCode, code).update(updRecord); + } + + @Override + public void fastSuccessWithInfo(Long instanceId, String code, String executeInfo) { + TaskInstanceDetailTb updRecord = new TaskInstanceDetailTb(); + updRecord.setFinishTime(new Date()); + updRecord.setExecuteInfo(executeInfo == null ? "执行成功" : executeInfo); + updRecord.setExecuteStatus(TaskStatusEnum.Success.getCode()); + lambdaUpdate().eq(TaskInstanceDetailTb::getInstanceId, instanceId).eq(TaskInstanceDetailTb::getBizCode, code).update(updRecord); + } + + @Override + public void fastStart(Long instanceId, String code, JobDataMap dataMap) { + TaskInstanceDetailTb updRecord = new TaskInstanceDetailTb(); + updRecord.setStartTime(new Date()); + updRecord.setExecuteInfo("运行中"); + updRecord.setExecuteParams(JSON.toJSONString(dataMap)); + updRecord.setExecuteStatus(TaskStatusEnum.Running.getCode()); + lambdaUpdate().eq(TaskInstanceDetailTb::getInstanceId, instanceId).eq(TaskInstanceDetailTb::getBizCode, code).update(updRecord); + } + + @Override + public List queryByInstanceIdAndBizCodeList(Long instanceId, List bizCodeList) { + if (CollUtil.isEmpty(bizCodeList)) { + return CollUtil.newArrayList(); + } + return lambdaQuery().eq(TaskInstanceDetailTb::getInstanceId, instanceId).in(TaskInstanceDetailTb::getBizCode, bizCodeList).list(); + } + + @Override + public List queryByInstanceId(Long instanceId) { + return lambdaQuery().eq(TaskInstanceDetailTb::getInstanceId, instanceId).orderByAsc(TaskInstanceDetailTb::getId).list(); + } +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskInstanceInfoServiceImpl.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskInstanceInfoServiceImpl.java new file mode 100644 index 00000000..8c08acd4 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskInstanceInfoServiceImpl.java @@ -0,0 +1,107 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.service.impl; + +import cn.odboy.task.constant.TaskStatusEnum; +import cn.odboy.task.dal.dataobject.TaskInstanceInfoTb; +import cn.odboy.task.dal.mysql.TaskInstanceInfoMapper; +import cn.odboy.task.service.TaskInstanceInfoService; +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.quartz.JobDataMap; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; + +/** + *

+ * 任务实例 服务实现类 + *

+ * + * @author codegen + * @since 2025-09-26 + */ +@Service +public class TaskInstanceInfoServiceImpl extends ServiceImpl implements TaskInstanceInfoService { + + @Override + public TaskInstanceInfoTb getRunningById(Long id) { + return lambdaQuery() + .eq(TaskInstanceInfoTb::getId, id) + .eq(TaskInstanceInfoTb::getStatus, TaskStatusEnum.Running.getCode()) + .orderByDesc(TaskInstanceInfoTb::getId) + .one(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void fastFailWithMessage(Long id, String errorMessage) { + TaskInstanceInfoTb updRecord = new TaskInstanceInfoTb(); + updRecord.setId(id); + updRecord.setFinishTime(new Date()); + updRecord.setStatus(TaskStatusEnum.Fail.getCode()); + updRecord.setErrorMessage(errorMessage); + updateById(updRecord); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void fastFailWithMessageData(Long id, String errorMessage, JobDataMap dataMap) { + TaskInstanceInfoTb updRecord = new TaskInstanceInfoTb(); + updRecord.setId(id); + updRecord.setFinishTime(new Date()); + updRecord.setStatus(TaskStatusEnum.Fail.getCode()); + updRecord.setErrorMessage(errorMessage); + updRecord.setJobData(JSON.toJSONString(dataMap)); + updateById(updRecord); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void fastSuccessWithData(Long id, JobDataMap dataMap) { + TaskInstanceInfoTb updRecord = new TaskInstanceInfoTb(); + updRecord.setId(id); + updRecord.setFinishTime(new Date()); + updRecord.setStatus(TaskStatusEnum.Success.getCode()); + updRecord.setJobData(JSON.toJSONString(dataMap)); + updateById(updRecord); + } + + @Override + public TaskInstanceInfoTb getLastRunningInstance(String contextName, String language, String envAlias, String changeType) { + return lambdaQuery() + .eq(TaskInstanceInfoTb::getContextName, contextName) + .eq(TaskInstanceInfoTb::getLanguage, language) + .eq(TaskInstanceInfoTb::getEnvAlias, envAlias) + .eq(TaskInstanceInfoTb::getChangeType, changeType) + .eq(TaskInstanceInfoTb::getStatus, TaskStatusEnum.Running.getCode()) + .orderByDesc(TaskInstanceInfoTb::getId) + .one(); + } + + @Override + public TaskInstanceInfoTb getLastHistoryInstance(String contextName, String language, String envAlias, String changeType) { + return lambdaQuery() + .eq(TaskInstanceInfoTb::getContextName, contextName) + .eq(TaskInstanceInfoTb::getLanguage, language) + .eq(TaskInstanceInfoTb::getEnvAlias, envAlias) + .eq(TaskInstanceInfoTb::getChangeType, changeType) + .orderByDesc(TaskInstanceInfoTb::getId) + .last("LIMIT 1") + .one(); + } +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskInstanceStepDetailServiceImpl.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskInstanceStepDetailServiceImpl.java new file mode 100644 index 00000000..fa4eedef --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskInstanceStepDetailServiceImpl.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.task.constant.TaskStatusEnum; +import cn.odboy.task.dal.dataobject.TaskInstanceStepDetailTb; +import cn.odboy.task.dal.mysql.TaskInstanceStepDetailMapper; +import cn.odboy.task.service.TaskInstanceStepDetailService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + *

+ * 任务实例步骤明细 服务实现类 + *

+ * + * @author codegen + * @since 2025-09-28 + */ +@Service +public class TaskInstanceStepDetailServiceImpl extends ServiceImpl implements TaskInstanceStepDetailService { + + @Override + public void success(Long instanceDetailId, String stepDesc) { + TaskInstanceStepDetailTb stepDetail = new TaskInstanceStepDetailTb(); + stepDetail.setInstanceDetailId(instanceDetailId); + stepDetail.setStepDesc(stepDesc); + stepDetail.setStepStatus(TaskStatusEnum.Success.getCode()); + save(stepDetail); + } + + @Override + public void fail(Long instanceDetailId, String stepDesc) { + TaskInstanceStepDetailTb stepDetail = new TaskInstanceStepDetailTb(); + stepDetail.setInstanceDetailId(instanceDetailId); + stepDetail.setStepDesc(stepDesc); + stepDetail.setStepStatus(TaskStatusEnum.Fail.getCode()); + save(stepDetail); + throw new BadRequestException(stepDesc); + } + + @Override + public void removeByInstanceDetailIds(List instanceDetailIds) { + if (CollUtil.isEmpty(instanceDetailIds)) { + return; + } + remove(new LambdaQueryWrapper() + .in(TaskInstanceStepDetailTb::getInstanceDetailId, instanceDetailIds) + ); + } +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskTemplateInfoServiceImpl.java b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskTemplateInfoServiceImpl.java new file mode 100644 index 00000000..2a8ed83f --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/java/cn/odboy/task/service/impl/TaskTemplateInfoServiceImpl.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.odboy.task.service.impl; + +import cn.odboy.task.dal.dataobject.TaskTemplateInfoTb; +import cn.odboy.task.dal.model.TaskTemplateInfoVo; +import cn.odboy.task.dal.mysql.TaskTemplateInfoMapper; +import cn.odboy.task.service.TaskTemplateInfoService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 任务模板 服务实现类 + *

+ * + * @author codegen + * @since 2025-09-28 + */ +@Service +public class TaskTemplateInfoServiceImpl extends ServiceImpl implements TaskTemplateInfoService { + + @Override + public TaskTemplateInfoVo getTemplateInfoByECL(String envAlias, String contextName, String language, String changeType) { + return baseMapper.selectTemplateInfoByECL(envAlias, contextName, language, changeType); + } +} diff --git a/cutejava-springboot3/cutejava-module-task/src/main/resources/mapper/TaskInstanceDetailMapper.xml b/cutejava-springboot3/cutejava-module-task/src/main/resources/mapper/TaskInstanceDetailMapper.xml new file mode 100644 index 00000000..9c151269 --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/resources/mapper/TaskInstanceDetailMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/cutejava-springboot3/cutejava-module-task/src/main/resources/mapper/TaskInstanceInfoMapper.xml b/cutejava-springboot3/cutejava-module-task/src/main/resources/mapper/TaskInstanceInfoMapper.xml new file mode 100644 index 00000000..4896649c --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/resources/mapper/TaskInstanceInfoMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/cutejava-springboot3/cutejava-module-task/src/main/resources/mapper/TaskTemplateInfoMapper.xml b/cutejava-springboot3/cutejava-module-task/src/main/resources/mapper/TaskTemplateInfoMapper.xml new file mode 100644 index 00000000..b0f24dbd --- /dev/null +++ b/cutejava-springboot3/cutejava-module-task/src/main/resources/mapper/TaskTemplateInfoMapper.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/cutejava-springboot3/cutejava-starter/pom.xml b/cutejava-springboot3/cutejava-starter/pom.xml new file mode 100644 index 00000000..25aa989b --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/pom.xml @@ -0,0 +1,41 @@ + + + + cutejava + cn.odboy + 1.4.1 + + 4.0.0 + + cutejava-starter + 启动模块 + + + + cn.odboy + cutejava-module-task + 1.4.1 + + + + + + + cutejava + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-starter/src/main/java/cn/odboy/AppRun.java b/cutejava-springboot3/cutejava-starter/src/main/java/cn/odboy/AppRun.java new file mode 100644 index 00000000..092d1cbe --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/main/java/cn/odboy/AppRun.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy; + +import cn.odboy.framework.context.BootApplication; +import io.swagger.annotations.Api; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; + +import java.net.UnknownHostException; + +@Api(hidden = true) +@EnableCaching +@SpringBootApplication +public class AppRun extends BootApplication { + + public static void main(String[] args) throws UnknownHostException { + SpringApplication springApplication = new SpringApplication(AppRun.class); + inited(springApplication.run(args)); + } +} diff --git a/cutejava-springboot3/cutejava-starter/src/main/java/cn/odboy/GenCode.java b/cutejava-springboot3/cutejava-starter/src/main/java/cn/odboy/GenCode.java new file mode 100644 index 00000000..62229e54 --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/main/java/cn/odboy/GenCode.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy; + +import cn.odboy.framework.mybatisplus.core.CsMpCmdGenUtil; + +import java.util.List; + +/** + * 代码生成入口 + */ +public class GenCode { + private static final String ADDR = "192.168.100.128"; + private static final Integer PORT = 23306; + private static final String DATABASE_NAME = "cutejava"; + private static final String DATABASE_USER = "root"; + private static final String DATABASE_PWD = "kd123456"; + private static final String URL = String.format("jdbc:mysql://%s:%s/%s", ADDR, PORT, DATABASE_NAME); + + public static void main(String[] args) { + CsMpCmdGenUtil generator = new CsMpCmdGenUtil(); + generator.setDatabaseUrl(URL); + generator.setDriverClassName("com.mysql.cj.jdbc.Driver"); + generator.setDatabaseUsername(DATABASE_USER); + generator.setDatabasePassword(DATABASE_PWD); + genTaskCode(generator); + } + + private static void genTaskCode(CsMpCmdGenUtil generator) { + generator.gen("task", "", List.of( + "task_instance_info", + "task_instance_detail", + "task_template_info", + "task_instance_step_detail" + )); + } +} diff --git a/cutejava-springboot3/cutejava-starter/src/main/java/cn/odboy/HealthCheckPointer.java b/cutejava-springboot3/cutejava-starter/src/main/java/cn/odboy/HealthCheckPointer.java new file mode 100644 index 00000000..284baad1 --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/main/java/cn/odboy/HealthCheckPointer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy; + +import cn.odboy.framework.monitor.service.CsHealthCheckPointService; +import cn.odboy.system.service.SystemDictService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +@Component +public class HealthCheckPointer implements CsHealthCheckPointService { + @Autowired + private SystemDictService systemDictService; + + /** + * 系统准备就绪 + */ + @Override + public ResponseEntity doReadiness() { + return ResponseEntity.ok(null); + } + + /** + * 系统是否正常 + */ + @Override + public ResponseEntity doLiveness() { + return ResponseEntity.ok(systemDictService.getById(1)); + } +} diff --git a/cutejava-springboot3/cutejava-starter/src/main/resources/application-dev.yml b/cutejava-springboot3/cutejava-starter/src/main/resources/application-dev.yml new file mode 100644 index 00000000..b317af3c --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/main/resources/application-dev.yml @@ -0,0 +1,258 @@ +server: + port: 8000 + http2: + # 启用 HTTP/2 支持,提升传输效率 + enabled: true + undertow: + threads: + # 工作线程数,默认设置为io-threads * 8。如果你的应用程序有很多同步阻塞操作,可以适当增加这个值 + worker: 8 + # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程 + # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载 + io: 4 + # 设置为true以使用直接内存(堆外内存)来存储缓冲区。这可以减少垃圾回收的开销。 + direct-buffers: true + # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理 + # 每块buffer的空间大小,越小的空间被利用越充分 + buffer-size: 1024 + accesslog: + # 关闭日志 + enabled: false + compression: + # 启用 GZIP 压缩,减少传输数据量 + enabled: true + # 需要压缩的 MIME 类型 + mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json + # 最小压缩响应大小(字节) + min-response-size: 1024 + # 启用优雅停机,并遵守spring.lifecycle.timeout-per-shutdown-phase属性中给出的超时 + # GRACEFUL (优雅): 当应用程序以"GRACEFUL"模式关闭时,它不会接受新的请求且会尝试完成所有正在进行的请求和处理,然后才会终止。这种方式适用于那些可能需要一些时间来清理资源或完成正在进行的任务的场景。 + # IMMEDIATE(立即): 当应用程序以"IMMEDIATE"模式关闭时,它会立即终止,而不管当前是否有任何活动任务或请求。这种方式适用于那些可以立即停止而不会造成严重问题的情况。 + shutdown: graceful +spring: + application: + name: cutejava + title: CuteJava + lifecycle: + # 采用java.time.Duration格式的值,如果在这个时间内,优雅停机没有停掉应用,超过这个时间就会强制停止应用 + timeout-per-shutdown-phase: 30s + freemarker: + # 是否检查模板位置是否存在 + check-template-location: false + # 模板编码 + charset: utf-8 + cache: + # 解决缓存异常 https://blog.csdn.net/jikeyeka/article/details/125804049 + type: redis + data: + redis: + repositories: + # 是否启用Redis存储(关闭防止出现 Multiple Spring Data modules found, entering strict repository configuration mode) + enabled: false + datasource: + druid: + db-type: com.alibaba.druid.pool.DruidDataSource + # sql监控 + driverClassName: com.p6spy.engine.spy.P6SpyDriver + url: jdbc:p6spy:mysql://192.168.100.128:23306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true +# driverClassName: com.mysql.cj.jdbc.Driver +# url: jdbc:mysql://192.168.100.128:23306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true + username: root + password: kd123456 + # 初始连接数,建议设置为与最小空闲连接数相同 + initial-size: 2 + # 最小空闲连接数,保持足够的空闲连接以应对请求 + min-idle: 2 + # 最大连接数,根据并发需求适当增加 + max-active: 8 + # 获取连接超时时间(毫秒),调整以满足响应时间要求 + max-wait: 3000 + # 启用KeepAlive机制,保持长连接 + keep-alive: true + # 连接有效性检测间隔时间(毫秒),定期检查连接的健康状态 + time-between-eviction-runs-millis: 60000 + # 连接在池中最小生存时间(毫秒),确保连接在池中至少存在一段时间 + min-evictable-idle-time-millis: 300000 + # 连接在池中最大生存时间(毫秒),防止连接在池中停留过长 + max-evictable-idle-time-millis: 900000 + # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除 + test-while-idle: true + # 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个 + test-on-borrow: true + # 是否在归还到池中前进行检验 + test-on-return: false + # 停用 com_ping 探活机制 + use-ping-method: false + # 检测连接是否有效 + validation-query: SELECT 1 + # 配置监控统计 + web-stat-filter: + enabled: true + url-pattern: /* + # 开启session统计功能 + session-stat-enable: true + exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" + # 缺省sessionStatMaxCount是1000个 + session-stat-max-count: 1000 + stat-view-servlet: + enabled: true + url-pattern: /druid/* + reset-enable: false + filter: + stat: + enabled: true + # 记录慢SQL + log-slow-sql: true + slow-sql-millis: 2000 + merge-sql: true + wall: + config: + multi-statement-allow: true + aop-patterns: "cn.odboy.mapper.*,cn.odboy.service.*,cn.odboy.*.mapper.*,cn.odboy.*.service.*" + redis: + database: ${REDIS_DB:1} + host: ${REDIS_HOST:192.168.100.128} + port: ${REDIS_PORT:26379} + password: ${REDIS_PWD:kd123456} + # 连接超时时间 + timeout: 5000 + # 连接池配置 + lettuce: + pool: + # 连接池最大连接数 + max-active: 30 + # 连接池最大阻塞等待时间(毫秒),负值表示没有限制 + max-wait: -1 + # 连接池中的最大空闲连接数 + max-idle: 20 + # 连接池中的最小空闲连接数 + min-idle: 1 + # 数据库一定不能区分大小写 + quartz: + # 必需,指定使用 JDBC 存储 + job-store-type: jdbc + properties: + org: + quartz: + scheduler: + # 必需,指定调度器实例的名称 + instanceName: ${spring.application.name} + # 必需,指定调度器实例的 ID + instanceId: auto + threadPool: + # 可选,线程池的线程数量,可以根据需要调整 + threadCount: 5 + jobStore: + # 可选,如果你不需要集群,可以去掉 + isClustered: true + # 可选,集群检查间隔时间,可以根据需要调整 + clusterCheckinInterval: 10000 + # 必需,指定 JDBC 驱动代理类 + driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate + # 可选,是否使用属性存储,可以根据需要调整 + useProperties: false + # 必需,指定表的前缀 + tablePrefix: qrtz_ + # 可选,指定误触发阈值,可以根据需要调整 + misfireThreshold: 60000 +# 集成mybatis-plus +mybatis-plus: + configuration: + # 开启 Mybatis 二级缓存,默认为 true + cache-enabled: false + # 设置本地缓存作用域, Mybatis 一级缓存, 默认为 SESSION + # 同一个 session 相同查询语句不会再次查询数据库 + # 微服务中, 建议设置为STATEMENT, 即关闭一级缓存 + local-cache-scope: STATEMENT + # 是否开启自动驼峰命名规则(camel case)映射, 即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射 + map-underscore-to-camel-case: true + # Sql日志 + # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + # 启动时是否检查 MyBatis XML 文件的存在, 默认不检查 + check-config-location: true + # MyBatis Mapper 所对应的 XML 文件位置 + # Maven 多模块项目的扫描路径需以 classpath*: 开头 (即加载多个 jar 包下的 XML 文件) + mapper-locations: classpath*:mapper/**/*.xml + # MyBatis-Plus 全局策略中的 DB 策略配置 + global-config: + db-config: + # 逻辑已删除值(逻辑删除下有效) + logic-delete-value: 0 + # 逻辑未删除值(逻辑删除下有效) + logic-not-delete-value: 1 + # 全局默认主键类型, 这里为自增主键 + id-type: auto + # 表名是否使用驼峰转下划线命名,只对表名生效 + table-underline: true + # 是否控制台 print mybatisplus-plus 的 LOGO + banner: true +# 框架配置 +app: + # 密码加密传输,前端公钥加密,后端私钥解密 + rsa: + private-key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A== + # 鉴权 + jwt: + # 必须使用最少88位的Base64对该令牌进行编码 cn.odboy.util.CsBase64SecretUtil + base64-secret: YzE5ZWNjMjlkMGY4NDc4MDlmYzE0Y2I0MTk4YTQ4YjY5NTU0OGQxODNjNjQ0MmU1YjBmZTYwYjljYzI0M2M4MTJkZjk3Y2ZlYjg3ZTQxMjM4MjEwY2ZiYzE4YmFiYmZlZDVhM2M1YmI3M2FmNDFmNWJmM2MyMTM1ZDNhMmRmYzY= + # 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html + token-validity-in-seconds: 14400000 + # token 续期检查时间范围(默认30分钟,单位毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期 + detect: 1800000 + # 续期时间范围,默认1小时,单位毫秒 + renew: 3600000 + # 登录 + login: + # 是否限制单用户登录 + single: false + # 验证码 + captchaSetting: + # 验证码类型配置 查看 LoginProperties 类 + code-type: arithmetic + # 登录图形验证码有效时间/分钟 + expiration: 2 + # 验证码高度 + width: 111 + # 验证码宽度 + height: 36 + # 内容长度 + length: 2 + # 字体名称,为空则使用默认字体 + font-name: + # 字体大小 + font-size: 25 + # 是否开启 swagger-ui + swagger: + enabled: true + # 针对@Async + async-task-pool: + # 核心线程池大小 + core-pool-size: 10 + # 最大线程数 + max-pool-size: 30 + # 活跃时间 + keep-alive-seconds: 60 + # 队列容量 + queue-capacity: 50 + # oss + oss: + max-size: 100 + minio: + endpoint: http://192.168.100.128:9000 + bucketName: cutejava + accessKey: 8o5oVzqvKby5TzaGcfmx + secretKey: 8BiK5UbmvXGJNVQ98CGiohHzT1N1FMRZqHRdJllj + captcha: + expireTime: 300 +# debug +#debug: true +# 日志配置 +logging: + logback: + rollingpolicy: + max-file-size: 100MB + max-history: 7 + total-size-cap: 1GB + level: + # root: debug + cn.odboy: info \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-starter/src/main/resources/application-prod.yml b/cutejava-springboot3/cutejava-starter/src/main/resources/application-prod.yml new file mode 100644 index 00000000..3a924d91 --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/main/resources/application-prod.yml @@ -0,0 +1,258 @@ +server: + port: 8000 + http2: + # 启用 HTTP/2 支持,提升传输效率 + enabled: true + undertow: + threads: + # 工作线程数,默认设置为io-threads * 8。如果你的应用程序有很多同步阻塞操作,可以适当增加这个值 + worker: 8 + # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程 + # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载 + io: 4 + # 设置为true以使用直接内存(堆外内存)来存储缓冲区。这可以减少垃圾回收的开销。 + direct-buffers: true + # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理 + # 每块buffer的空间大小,越小的空间被利用越充分 + buffer-size: 1024 + accesslog: + # 关闭日志 + enabled: false + compression: + # 启用 GZIP 压缩,减少传输数据量 + enabled: true + # 需要压缩的 MIME 类型 + mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json + # 最小压缩响应大小(字节) + min-response-size: 1024 + # 启用优雅停机,并遵守spring.lifecycle.timeout-per-shutdown-phase属性中给出的超时 + # GRACEFUL (优雅): 当应用程序以"GRACEFUL"模式关闭时,它不会接受新的请求且会尝试完成所有正在进行的请求和处理,然后才会终止。这种方式适用于那些可能需要一些时间来清理资源或完成正在进行的任务的场景。 + # IMMEDIATE(立即): 当应用程序以"IMMEDIATE"模式关闭时,它会立即终止,而不管当前是否有任何活动任务或请求。这种方式适用于那些可以立即停止而不会造成严重问题的情况。 + shutdown: graceful +spring: + application: + name: cutejava + title: CuteJava + lifecycle: + # 采用java.time.Duration格式的值,如果在这个时间内,优雅停机没有停掉应用,超过这个时间就会强制停止应用 + timeout-per-shutdown-phase: 30s + freemarker: + # 是否检查模板位置是否存在 + check-template-location: false + # 模板编码 + charset: utf-8 + cache: + # 解决缓存异常 https://blog.csdn.net/jikeyeka/article/details/125804049 + type: redis + data: + redis: + repositories: + # 是否启用Redis存储(关闭防止出现 Multiple Spring Data modules found, entering strict repository configuration mode) + enabled: false + datasource: + druid: + db-type: com.alibaba.druid.pool.DruidDataSource + # sql监控 + # driverClassName: com.p6spy.engine.spy.P6SpyDriver + # url: jdbc:p6spy:mysql://192.168.100.128:23306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true + driverClassName: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://192.168.100.128:23306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true + username: root + password: kd123456 + # 初始连接数,建议设置为与最小空闲连接数相同 + initial-size: 2 + # 最小空闲连接数,保持足够的空闲连接以应对请求 + min-idle: 2 + # 最大连接数,根据并发需求适当增加 + max-active: 8 + # 获取连接超时时间(毫秒),调整以满足响应时间要求 + max-wait: 3000 + # 启用KeepAlive机制,保持长连接 + keep-alive: true + # 连接有效性检测间隔时间(毫秒),定期检查连接的健康状态 + time-between-eviction-runs-millis: 60000 + # 连接在池中最小生存时间(毫秒),确保连接在池中至少存在一段时间 + min-evictable-idle-time-millis: 300000 + # 连接在池中最大生存时间(毫秒),防止连接在池中停留过长 + max-evictable-idle-time-millis: 900000 + # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除 + test-while-idle: true + # 指明是否在从池中取出连接前进行检验,如果检验失败, 则从池中去除连接并尝试取出另一个 + test-on-borrow: true + # 是否在归还到池中前进行检验 + test-on-return: false + # 停用 com_ping 探活机制 + use-ping-method: false + # 检测连接是否有效 + validation-query: SELECT 1 + # 配置监控统计 + web-stat-filter: + enabled: true + url-pattern: /* + # 开启session统计功能 + session-stat-enable: true + exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" + # 缺省sessionStatMaxCount是1000个 + session-stat-max-count: 1000 + stat-view-servlet: + enabled: true + url-pattern: /druid/* + reset-enable: false + filter: + stat: + enabled: true + # 记录慢SQL + log-slow-sql: true + slow-sql-millis: 2000 + merge-sql: true + wall: + config: + multi-statement-allow: true + aop-patterns: "cn.odboy.mapper.*,cn.odboy.service.*,cn.odboy.*.mapper.*,cn.odboy.*.service.*" + redis: + database: ${REDIS_DB:1} + host: ${REDIS_HOST:192.168.100.128} + port: ${REDIS_PORT:26379} + password: ${REDIS_PWD:kd123456} + # 连接超时时间 + timeout: 5000 + # 连接池配置 + lettuce: + pool: + # 连接池最大连接数 + max-active: 30 + # 连接池最大阻塞等待时间(毫秒),负值表示没有限制 + max-wait: -1 + # 连接池中的最大空闲连接数 + max-idle: 20 + # 连接池中的最小空闲连接数 + min-idle: 1 + # 数据库一定不能区分大小写 + quartz: + # 必需,指定使用 JDBC 存储 + job-store-type: jdbc + properties: + org: + quartz: + scheduler: + # 必需,指定调度器实例的名称 + instanceName: cutejava + # 必需,指定调度器实例的 ID + instanceId: auto + threadPool: + # 可选,线程池的线程数量,可以根据需要调整 + threadCount: 5 + jobStore: + # 可选,如果你不需要集群,可以去掉 + isClustered: true + # 可选,集群检查间隔时间,可以根据需要调整 + clusterCheckinInterval: 10000 + # 必需,指定 JDBC 驱动代理类 + driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate + # 可选,是否使用属性存储,可以根据需要调整 + useProperties: false + # 必需,指定表的前缀 + tablePrefix: qrtz_ + # 可选,指定误触发阈值,可以根据需要调整 + misfireThreshold: 60000 +# 集成mybatis-plus +mybatis-plus: + configuration: + # 开启 Mybatis 二级缓存,默认为 true + cache-enabled: false + # 设置本地缓存作用域, Mybatis 一级缓存, 默认为 SESSION + # 同一个 session 相同查询语句不会再次查询数据库 + # 微服务中, 建议设置为STATEMENT, 即关闭一级缓存 + local-cache-scope: STATEMENT + # 是否开启自动驼峰命名规则(camel case)映射, 即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射 + map-underscore-to-camel-case: true + # Sql日志 + # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + # 启动时是否检查 MyBatis XML 文件的存在, 默认不检查 + check-config-location: true + # MyBatis Mapper 所对应的 XML 文件位置 + # Maven 多模块项目的扫描路径需以 classpath*: 开头 (即加载多个 jar 包下的 XML 文件) + mapper-locations: classpath*:mapper/**/*.xml + # MyBatis-Plus 全局策略中的 DB 策略配置 + global-config: + db-config: + # 逻辑已删除值(逻辑删除下有效) + logic-delete-value: 0 + # 逻辑未删除值(逻辑删除下有效) + logic-not-delete-value: 1 + # 全局默认主键类型, 这里为自增主键 + id-type: auto + # 表名是否使用驼峰转下划线命名,只对表名生效 + table-underline: true + # 是否控制台 print mybatisplus-plus 的 LOGO + banner: true +# 框架配置 +app: + # 密码加密传输,前端公钥加密,后端私钥解密 + rsa: + private-key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A== + # 鉴权 + jwt: + # 必须使用最少88位的Base64对该令牌进行编码 + base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI= + # 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html + token-validity-in-seconds: 14400000 + # token 续期检查时间范围(默认30分钟,单位毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期 + detect: 1800000 + # 续期时间范围,默认1小时,单位毫秒 + renew: 3600000 + # 登录 + login: + # 是否限制单用户登录 + single: false + # 验证码 + captchaSetting: + # 验证码类型配置 查看 LoginProperties 类 + code-type: arithmetic + # 登录图形验证码有效时间/分钟 + expiration: 2 + # 验证码高度 + width: 111 + # 验证码宽度 + height: 36 + # 内容长度 + length: 2 + # 字体名称,为空则使用默认字体 + font-name: + # 字体大小 + font-size: 25 + # 是否开启 swagger-ui + swagger: + enabled: true + # 针对@Async + async-task-pool: + # 核心线程池大小 + core-pool-size: 10 + # 最大线程数 + max-pool-size: 30 + # 活跃时间 + keep-alive-seconds: 60 + # 队列容量 + queue-capacity: 50 + # oss + oss: + max-size: 100 + minio: + endpoint: http://192.168.100.128:9000 + bucketName: cutejava + accessKey: 8o5oVzqvKby5TzaGcfmx + secretKey: 8BiK5UbmvXGJNVQ98CGiohHzT1N1FMRZqHRdJllj + captcha: + expireTime: 300 +# debug +#debug: true +# 日志配置 +logging: + logback: + rollingpolicy: + max-file-size: 100MB + max-history: 7 + total-size-cap: 1GB + level: + # root: debug + cn.odboy: info \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-starter/src/main/resources/application.yml b/cutejava-springboot3/cutejava-starter/src/main/resources/application.yml new file mode 100644 index 00000000..279f2ff9 --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/main/resources/application.yml @@ -0,0 +1,5 @@ +spring: + profiles: + # 激活的环境 + active: dev + diff --git a/cutejava-springboot3/cutejava-starter/src/main/resources/banner.txt b/cutejava-springboot3/cutejava-starter/src/main/resources/banner.txt new file mode 100644 index 00000000..b6fb25a2 --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/main/resources/banner.txt @@ -0,0 +1,8 @@ + _____ _ _ _____ _____ _ ___ _ _ ___ +/ ___| | | | | |_ _| | ____| | | / | | | / / / | +| | | | | | | | | |__ | | / /| | | | / / / /| | +| | | | | | | | | __| _ | | / / | | | | / / / / | | +| |___ | |_| | | | | |___ | |_| | / / | | | |/ / / / | | +\_____| \_____/ |_| |_____| \_____/ /_/ |_| |___/ /_/ |_| + +Spring Boot Version: (${spring-boot.version}),CuteJava Version: (1.4.1) \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-starter/src/main/resources/logback.xml b/cutejava-springboot3/cutejava-starter/src/main/resources/logback.xml new file mode 100644 index 00000000..ca6cca03 --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/main/resources/logback.xml @@ -0,0 +1,43 @@ + + + CuteJava + + + + + + + + + + + + + + + + + ${log.pattern} + ${log.charset} + + + + + ${log.home}/${log.name}.log + + ${log.home}/${log.name}.log.%d{yyyy-MM-dd}.%i.log + 90 + 100MB + + + ${log.pattern} + + + + + + + + + + \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-starter/src/main/resources/spy.properties b/cutejava-springboot3/cutejava-starter/src/main/resources/spy.properties new file mode 100644 index 00000000..784f0677 --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/main/resources/spy.properties @@ -0,0 +1,20 @@ +# 应用的拦截模块 +modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory +# 自定义日志打印 +logMessageFormat=cn.odboy.framework.mybatisplus.config.MpCustomP6SpyLogger +# 日志输出到控制台 +appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger +# 日期格式 +dateformat=yyyy-MM-dd HH:mm:ss +# 实际驱动 可多个 +driverlist=com.mysql.cj.jdbc.Driver +# 是否开启慢SQL记录 +outagedetection=true +# 慢SQL记录标准 2 秒 +outagedetectioninterval=2 +# 是否过滤 Log +filter=true +# 过滤 Log 时所排除的 sql 关键字,以逗号分隔 +exclude=SELECT 1,INSERT INTO system_quartz_log +# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. +excludecategories=info,debug,result,commit,resultset \ No newline at end of file diff --git a/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/MyBatisMapperTests.java b/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/MyBatisMapperTests.java new file mode 100644 index 00000000..dd2d9dd8 --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/MyBatisMapperTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.odboy.system.dal.dataobject.SystemUserTb; +import cn.odboy.system.dal.mysql.SystemUserMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Date; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class MyBatisMapperTests { + @Autowired + private SystemUserMapper systemUserMapper; + + @Test + public void testMapper1() { + // SELECT * FROM system_user WHERE (create_time > '2025-08-18T14:48:18.400+0800'); + systemUserMapper.selectList(new LambdaQueryWrapper().gt(SystemUserTb::getCreateTime, new Date())); + + // SELECT * FROM system_user WHERE (create_time > '2025-08-18T14:50:16.786+0800'); + systemUserMapper.selectList(new LambdaQueryWrapper().gt(SystemUserTb::getCreateTime, DateTime.now())); + + // SELECT * FROM system_user WHERE (create_time > '2025-08-18 14:53:46'); + systemUserMapper.selectList(new LambdaQueryWrapper().gt( + SystemUserTb::getCreateTime, + DateUtil.formatDateTime(new Date()))); + + // SELECT * FROM system_user WHERE (create_time > '2025-08-18'); + systemUserMapper.selectList(new LambdaQueryWrapper().gt( + SystemUserTb::getCreateTime, + DateUtil.formatDate(new Date()))); + } +} + diff --git a/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/SystemApplicationTests.java b/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/SystemApplicationTests.java new file mode 100644 index 00000000..ba7d8c4c --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/SystemApplicationTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy; + +import cn.hutool.core.collection.CollUtil; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.dataobject.SystemJobTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import cn.odboy.system.dal.dataobject.SystemUserTb; +import cn.odboy.system.service.SystemUserService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.Set; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SystemApplicationTests { + @Autowired + private PasswordEncoder passwordEncoder; + @Autowired + private SystemUserService systemUserService; + + @Test + public void contextLoads() { + SystemDeptTb systemDept = new SystemDeptTb(); + systemDept.setId(2L); + + SystemJobTb systemJob = new SystemJobTb(); + systemJob.setId(11L); + Set systemJobs = CollUtil.newHashSet(systemJob); + + SystemRoleTb systemRole = new SystemRoleTb(); + systemRole.setId(2L); + Set systemRoles = CollUtil.newHashSet(systemRole); + + String pwd = passwordEncoder.encode("123456"); + for (int i = 100; i < 200; i++) { + SystemUserTb systemUser = new SystemUserTb(); + systemUser.setDept(systemDept); + systemUser.setJobs(systemJobs); + systemUser.setRoles(systemRoles); + systemUser.setUsername("odboy" + i); + systemUser.setEmail("1943815" + i + "@qq.com"); + systemUser.setPhone("18797874" + i); + systemUser.setPassword(pwd); + systemUser.setEnabled(true); + systemUserService.saveUser(systemUser); + } + } + + public static void main(String[] args) { + } +} + diff --git a/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/CsDesEncryptUtilTest.java b/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/CsDesEncryptUtilTest.java new file mode 100644 index 00000000..3110e34a --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/CsDesEncryptUtilTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +import static cn.odboy.util.CsDesEncryptUtil.desDecrypt; +import static cn.odboy.util.CsDesEncryptUtil.desEncrypt; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CsDesEncryptUtilTest { + + @SneakyThrows + public static void main(String[] args) { + System.err.println(desEncrypt("abc12345678")); + } + + /** + * 对称加密 + */ + @Test + public void testDesEncrypt() { + try { + assertEquals("7772841DC6099402", desEncrypt("abc12345678")); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 对称解密 + */ + @Test + public void testDesDecrypt() { + try { + assertEquals("abc12345678", desDecrypt("7772841DC6099402")); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/CsFileUtilTest.java b/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/CsFileUtilTest.java new file mode 100644 index 00000000..534776b7 --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/CsFileUtilTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockMultipartFile; + +import static cn.odboy.util.CsFileUtil.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CsFileUtilTest { + + @Test + public void testToFile() { + long retval = toFile(new MockMultipartFile("foo", (byte[]) null)).getTotalSpace(); + assertEquals(500695072768L, retval); + } + + @Test + public void testGetExtensionName() { + assertEquals("foo", getSuffix("foo")); + assertEquals("exe", getSuffix("bar.exe")); + } + + @Test + public void testGetFileNameNoEx() { + assertEquals("foo", getPrefix("foo")); + assertEquals("bar", getPrefix("bar.txt")); + } + + @Test + public void testGetSize() { + assertEquals("1000B ", getSize(1000)); + assertEquals("1.00KB ", getSize(1024)); + assertEquals("1.00MB ", getSize(1048576)); + assertEquals("1.00GB ", getSize(1073741824)); + } +} diff --git a/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/CsStringUtilTest.java b/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/CsStringUtilTest.java new file mode 100644 index 00000000..2ff735d6 --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/CsStringUtilTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class CsStringUtilTest { + + @Test + public void testToCamelCase() { + assertNull(CsStringUtil.toCamelCase(null)); + } + + @Test + public void testToCapitalizeCamelCase() { + assertNull(CsStringUtil.toCapitalizeCamelCase(null)); + assertEquals("HelloWorld", CsStringUtil.toCapitalizeCamelCase("hello_world")); + } + + @Test + public void testToUnderScoreCase() { + assertNull(CsStringUtil.toUnderScoreCase(null)); + assertEquals("hello_world", CsStringUtil.toUnderScoreCase("helloWorld")); + assertEquals("\u0000\u0000", CsStringUtil.toUnderScoreCase("\u0000\u0000")); + assertEquals("\u0000_a", CsStringUtil.toUnderScoreCase("\u0000A")); + } + + @Test + public void testGetWeekDay() { + SimpleDateFormat simpleDateformat = new SimpleDateFormat("E"); + assertEquals(simpleDateformat.format(new Date()), CsStringUtil.getWeekDay()); + } + + @Test + public void testGetIP() { + Assertions.assertEquals("127.0.0.1", CsBrowserUtil.getIp(new MockHttpServletRequest())); + } +} diff --git a/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/WorkTimeTests.java b/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/WorkTimeTests.java new file mode 100644 index 00000000..e1f63909 --- /dev/null +++ b/cutejava-springboot3/cutejava-starter/src/test/java/cn/odboy/util/WorkTimeTests.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2025 Odboy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.odboy.util; + +/** + * 钉钉 Teambition 实际工时数计算 + */ +public class WorkTimeTests { + public static void main(String[] args) { + // 总工作时长(时) + double totalHour = 11; + // 实际工作时长(天),一天8小时工作制 + double actualHour = totalHour / 8.0; + System.err.println("actualHour=" + actualHour + " 天"); + } +} diff --git a/cutejava-springboot3/pom.xml b/cutejava-springboot3/pom.xml new file mode 100644 index 00000000..dc6528d1 --- /dev/null +++ b/cutejava-springboot3/pom.xml @@ -0,0 +1,473 @@ + + + 4.0.0 + cn.odboy + cutejava + pom + 1.4.1 + + + + cutejava-framework + cutejava-module-system + cutejava-module-task + cutejava-starter + + + CuteJava 后台 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.18 + + + + UTF-8 + UTF-8 + UTF-8 + 11 + 1.2.9 + 2.0.57 + 1.2.27 + 2.11.1 + 3.5.14 + 5.8.40 + + 0.12.6 + + 5.8.0 + 1.4.7 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-json + + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-undertow + + + io.undertow + undertow-core + + + + + io.undertow + undertow-core + 2.3.18.Final + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.boot + spring-boot-starter-security + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.springframework.boot + spring-boot-starter-cache + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-jsqlparser + ${mybatis-plus.version} + + + org.springframework.boot + spring-boot-starter-freemarker + + + org.freemarker + freemarker + + + + + org.freemarker + freemarker + 2.3.34 + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus.version} + + + + + + org.apache.commons + commons-pool2 + ${commons-pool2.version} + + + org.apache.commons + commons-lang3 + + + + + p6spy + p6spy + 3.9.1 + + + + + com.github.xiaoymin + knife4j-spring-boot-starter + 3.0.3 + + + + io.swagger + swagger-annotations + + + + + io.swagger + swagger-annotations + 1.5.22 + + + + + com.mysql + mysql-connector-j + 9.3.0 + runtime + + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + + net.dreamlu + mica-ip2region + 2.7.18.9 + + + + + org.aspectj + aspectjrt + 1.9.7 + + + + org.aspectj + aspectjweaver + 1.9.7 + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + + + + + + + + + + + + + + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson2.version} + + + + com.alibaba.fastjson2 + fastjson2-extension-spring5 + ${fastjson2.version} + + + + + com.github.whvcse + easy-captcha + 1.6.2 + + + + org.apache.commons + commons-text + 1.13.0 + + + + + com.alibaba + transmittable-thread-local + 2.14.5 + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + cn.hutool + hutool-all + ${hutool.version} + + + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + + + + + ch.ethz.ganymed + ganymed-ssh2 + build210 + + + com.jcraft + jsch + 0.1.55 + + + + + com.github.oshi + oshi-core + 6.6.5 + + + + + javax.mail + mail + ${mail.version} + + + + + org.springframework.boot + spring-boot-starter-quartz + + + org.quartz-scheduler + quartz + + + + + org.quartz-scheduler + quartz + 2.5.0 + + + com.mchange + c3p0 + 0.10.1 + + + + + commons-net + commons-net + 3.11.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + joda-time + joda-time + 2.12.7 + + + + + io.github.biezhi + TinyPinyin + 2.0.3.RELEASE + + + + + + cn.idev.excel + fastexcel + 1.2.0 + compile + + + + + org.mozilla + rhino + 1.8.0 + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + + public + aliyun nexus + http://maven.aliyun.com/nexus/content/groups/public/ + + true + + + + + + + public + aliyun nexus + http://maven.aliyun.com/nexus/content/groups/public/ + + true + + + false + + + +