From f9a8c58b685d3dfd3cc2787ea54815cbad93a199 Mon Sep 17 00:00:00 2001 From: Odboy Date: Thu, 26 Feb 2026 21:56:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(framework):=20=E6=B7=BB=E5=8A=A0springboot?= =?UTF-8?q?4=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cutejava-springboot4/.gitignore | 9 + cutejava-springboot4/LICENSE | 191 ++++ .../cutejava-framework/pom.xml | 12 + .../cn/odboy/annotation/AnonymousAccess.java | 34 + .../annotation/AnonymousDeleteMapping.java | 77 ++ .../odboy/annotation/AnonymousGetMapping.java | 77 ++ .../annotation/AnonymousPatchMapping.java | 77 ++ .../annotation/AnonymousPostMapping.java | 77 ++ .../odboy/annotation/AnonymousPutMapping.java | 77 ++ .../src/main/java/cn/odboy/base/KitArgs.java | 45 + .../cn/odboy/base/KitBaseCreateTimeTb.java | 34 + .../java/cn/odboy/base/KitBaseTimeTb.java | 37 + .../cn/odboy/base/KitBaseUpdateTimeTb.java | 34 + .../odboy/base/KitBaseUserCreateTimeTb.java | 67 ++ .../cn/odboy/base/KitBaseUserTimeLogicTb.java | 57 ++ .../java/cn/odboy/base/KitBaseUserTimeTb.java | 75 ++ .../main/java/cn/odboy/base/KitObject.java | 29 + .../main/java/cn/odboy/base/KitPageArgs.java | 89 ++ .../java/cn/odboy/base/KitPageResult.java | 51 + .../java/cn/odboy/base/KitSelectOptionVo.java | 41 + .../cn/odboy/constant/CaptchaCodeEnum.java | 42 + .../java/cn/odboy/constant/FileTypeEnum.java | 28 + .../cn/odboy/constant/RequestMethodEnum.java | 61 ++ .../java/cn/odboy/constant/SystemConst.java | 51 + .../odboy/feature/KitDynamicTableColumn.java | 43 + .../feature/KitDynamicTableResponse.java | 172 ++++ .../cn/odboy/feature/KitDynamicTableUtil.java | 65 ++ .../context/BeanLoadFinishListener.java | 38 + .../framework/context/BootApplication.java | 55 ++ .../framework/context/KitRequestHolder.java | 34 + .../framework/context/KitScriptHelper.java | 42 + .../context/KitSpringBeanHolder.java | 169 ++++ .../exception/BadRequestException.java | 43 + .../framework/exception/ServerException.java | 40 + .../framework/exception/handler/ApiError.java | 43 + .../handler/GlobalExceptionHandler.java | 102 ++ .../framework/logging/ExecutionTimeLog.java | 45 + .../logging/ExecutionTimeLogAspect.java | 54 ++ .../controller/HealthCheckController.java | 61 ++ .../service/KitHealthCheckPointService.java | 32 + .../mybatisplus/config/DataTypeEnum.java | 120 +++ .../mybatisplus/config/KitMpQTypeEnum.java | 75 ++ .../mybatisplus/config/KitMpQuery.java | 47 + .../config/MpCustomP6SpyLogger.java | 69 ++ .../config/MpInsertBatchSqlInjector.java | 45 + .../mybatisplus/config/MybatisPlusConfig.java | 56 ++ .../mybatisplus/core/KitMpCmdGenUtil.java | 213 +++++ .../mybatisplus/core/KitMpQUtil.java | 232 +++++ .../core/interfaces/KitMpMapper.java | 124 +++ .../core/interfaces/KitMpService.java | 139 +++ .../core/interfaces/KitMpServiceImpl.java | 118 +++ .../framework/properties/AppProperties.java | 52 ++ .../properties/model/CaptchaModel.java | 30 + .../model/ContentRsaEncodeSettingModel.java | 30 + .../properties/model/JwtAuthSettingModel.java | 45 + .../properties/model/OSSConfigModel.java | 42 + .../properties/model/StorageOSSModel.java | 30 + .../model/SwaggerApiDocSettingModel.java | 31 + .../model/ThreadPoolSettingModel.java | 48 + .../model/UserLoginCaptchaSettingModel.java | 126 +++ .../model/UserLoginSettingModel.java | 36 + .../framework/quartz/config/QuartzConfig.java | 64 ++ .../framework/quartz/core/QuartzRunnable.java | 59 ++ .../redis/DemoMessageSubscriber.java | 33 + .../odboy/framework/redis/KitRedisHelper.java | 881 ++++++++++++++++++ .../framework/redis/RedisConfiguration.java | 194 ++++ .../framework/redis/RedisMessageConfig.java | 66 ++ .../server/config/AsyncTheadPoolConfig.java | 101 ++ .../DateStringArraysToListDateConverter.java | 47 + .../converters/DateStringToDateConverter.java | 43 + .../server/core/ConfigurerAdapter.java | 147 +++ .../server/core/KitFileLocalUploadHelper.java | 61 ++ .../websocket/KitWsBizCodeConst.java | 29 + .../websocket/KitWsMessagePublisher.java | 55 ++ .../websocket/WsMessageSubscriber.java | 51 + .../config/WebSocketEndpointConfig.java | 36 + .../websocket/context/KitWsClientManager.java | 64 ++ .../websocket/context/KitWsMessage.java | 38 + .../websocket/context/KitWsServer.java | 159 ++++ .../framework/websocket/model/WsSidVo.java | 30 + .../websocket/util/WsMessageUtil.java | 44 + .../java/cn/odboy/util/KitAnonTagUtil.java | 112 +++ .../cn/odboy/util/KitBase64SecretUtil.java | 48 + .../main/java/cn/odboy/util/KitBeanUtil.java | 58 ++ .../java/cn/odboy/util/KitBigDecimalUtil.java | 183 ++++ .../java/cn/odboy/util/KitBrowserUtil.java | 73 ++ .../main/java/cn/odboy/util/KitClassUtil.java | 36 + .../main/java/cn/odboy/util/KitCloseUtil.java | 46 + .../main/java/cn/odboy/util/KitCollUtil.java | 223 +++++ .../main/java/cn/odboy/util/KitDateUtil.java | 166 ++++ .../java/cn/odboy/util/KitDesEncryptUtil.java | 94 ++ .../cn/odboy/util/KitDistributedIdUtil.java | 196 ++++ .../main/java/cn/odboy/util/KitFileUtil.java | 400 ++++++++ .../main/java/cn/odboy/util/KitIPUtil.java | 89 ++ .../main/java/cn/odboy/util/KitJsonUtil.java | 49 + .../main/java/cn/odboy/util/KitListUtil.java | 68 ++ .../java/cn/odboy/util/KitLogFmtUtil.java | 51 + .../main/java/cn/odboy/util/KitPageUtil.java | 75 ++ .../odboy/util/KitResourceTemplateUtil.java | 42 + .../odboy/util/KitReturnValueHandleUtil.java | 71 ++ .../java/cn/odboy/util/KitRsaEncryptUtil.java | 217 +++++ .../java/cn/odboy/util/KitStringUtil.java | 98 ++ .../java/cn/odboy/util/KitSystemUtil.java | 41 + .../java/cn/odboy/util/KitThreadUtil.java | 17 + .../main/java/cn/odboy/util/KitValidUtil.java | 184 ++++ .../cn/odboy/util/xlsx/KitExcelExporter.java | 101 ++ .../util/xlsx/KitExcelPageWriteHelper.java | 36 + .../KitXlsxExportDataTransferRowHandler.java | 8 + .../cn/odboy/util/xlsx/KitXlsxExportUtil.java | 62 ++ .../cutejava-module-system/pom.xml | 25 + .../system/application/CuteDeptTreeApi.java | 46 + .../system/application/CuteFileUploadApi.java | 59 ++ .../application/CuteProductLineSelectApi.java | 42 + .../CuteProductLineSelectProApi.java | 42 + .../system/application/CuteUserSelectApi.java | 56 ++ .../application/model/CuteDeptTreeVo.java | 63 ++ .../model/CuteProductLineSelectProVo.java | 35 + .../model/CuteProductLineSelectVo.java | 38 + .../application/model/CuteUserSelectVo.java | 46 + .../application/service/CuteBaseService.java | 39 + .../service/CuteDeptTreeService.java | 80 ++ .../service/CuteFileUploadService.java | 82 ++ .../CuteProductLineSelectProService.java | 84 ++ .../service/CuteProductLineSelectService.java | 85 ++ .../service/CuteUserSelectService.java | 83 ++ .../odboy/system/constant/SystemCacheKey.java | 35 + .../system/constant/SystemCaptchaBizEnum.java | 56 ++ .../system/constant/SystemDataScopeEnum.java | 41 + .../constant/SystemTransferProtocolConst.java | 23 + .../system/constant/SystemYesOrNoChConst.java | 22 + .../odboy/system/constant/SystemZhConst.java | 24 + .../controller/SystemAuthController.java | 58 ++ .../controller/SystemCaptchaController.java | 63 ++ .../controller/SystemDeptController.java | 100 ++ .../controller/SystemDictController.java | 100 ++ .../SystemDictDetailController.java | 79 ++ .../controller/SystemEmailController.java | 64 ++ .../controller/SystemJobController.java | 90 ++ .../SystemLocalStorageController.java | 97 ++ .../controller/SystemMenuController.java | 133 +++ .../controller/SystemMonitorController.java | 45 + .../controller/SystemOnlineController.java | 72 ++ .../SystemOperationLogController.java | 50 + .../SystemOssStorageController.java | 83 ++ .../controller/SystemQuartzJobController.java | 130 +++ .../controller/SystemRoleController.java | 121 +++ .../controller/SystemUserController.java | 149 +++ .../system/dal/dataobject/SystemDeptTb.java | 82 ++ .../dal/dataobject/SystemDictDetailTb.java | 46 + .../system/dal/dataobject/SystemDictTb.java | 45 + .../dal/dataobject/SystemEmailConfigTb.java | 51 + .../system/dal/dataobject/SystemJobTb.java | 64 ++ .../dal/dataobject/SystemLocalStorageTb.java | 48 + .../system/dal/dataobject/SystemMenuTb.java | 97 ++ .../dal/dataobject/SystemOperationLogTb.java | 102 ++ .../dal/dataobject/SystemOssStorageTb.java | 81 ++ .../dal/dataobject/SystemQuartzJobTb.java | 72 ++ .../dal/dataobject/SystemQuartzLogTb.java | 56 ++ .../dal/dataobject/SystemRoleDeptTb.java | 33 + .../dal/dataobject/SystemRoleMenuTb.java | 35 + .../system/dal/dataobject/SystemRoleTb.java | 71 ++ .../dal/dataobject/SystemUserJobTb.java | 35 + .../dal/dataobject/SystemUserRoleTb.java | 33 + .../system/dal/dataobject/SystemUserTb.java | 100 ++ .../model/export/SystemDeptExportRowVo.java | 34 + .../model/export/SystemDictExportRowVo.java | 38 + .../model/export/SystemJobExportRowVo.java | 34 + .../export/SystemLocalStorageExportRowVo.java | 40 + .../model/export/SystemMenuExportRowVo.java | 42 + .../export/SystemOssStorageExportRowVo.java | 44 + .../export/SystemQuartzJobExportRowVo.java | 44 + .../export/SystemQuartzLogExportRowVo.java | 46 + .../model/export/SystemRoleExportRowVo.java | 36 + .../model/export/SystemUserExportRowVo.java | 46 + .../export/SystemUserOnlineExportRowVo.java | 39 + .../request/SystemCheckEmailCaptchaArgs.java | 33 + .../model/request/SystemCreateDeptArgs.java | 36 + .../model/request/SystemCreateDictArgs.java | 30 + .../request/SystemCreateDictDetailArgs.java | 37 + .../model/request/SystemCreateJobArgs.java | 33 + .../model/request/SystemCreateRoleArgs.java | 42 + .../model/request/SystemQueryDeptArgs.java | 38 + .../model/request/SystemQueryDictArgs.java | 26 + .../request/SystemQueryDictDetailArgs.java | 31 + .../dal/model/request/SystemQueryJobArgs.java | 34 + .../model/request/SystemQueryMenuArgs.java | 34 + .../request/SystemQueryOperationLogArgs.java | 26 + .../request/SystemQueryQuartzJobArgs.java | 32 + .../model/request/SystemQueryRoleArgs.java | 30 + .../model/request/SystemQueryStorageArgs.java | 34 + .../model/request/SystemQueryUserArgs.java | 43 + .../model/request/SystemSendEmailArgs.java | 43 + .../request/SystemUpdateQuartzJobArgs.java | 46 + .../request/SystemUpdateUserPasswordArgs.java | 31 + .../model/request/SystemUserLoginArgs.java | 37 + .../dal/model/response/SystemAuthVo.java | 31 + .../dal/model/response/SystemCaptchaVo.java | 37 + .../dal/model/response/SystemDeptVo.java | 83 ++ .../model/response/SystemDictDetailVo.java | 43 + .../dal/model/response/SystemMenuMetaVo.java | 35 + .../model/response/SystemMenuRouterVo.java | 47 + .../dal/model/response/SystemMenuVo.java | 99 ++ .../model/response/SystemOssStorageVo.java | 57 ++ .../dal/model/response/SystemRoleCodeVo.java | 34 + .../dal/model/response/SystemRoleVo.java | 70 ++ .../model/response/SystemSimpleUserVo.java | 55 ++ .../dal/model/response/SystemUserInfoVo.java | 36 + .../dal/model/response/SystemUserJwtVo.java | 77 ++ .../model/response/SystemUserOnlineVo.java | 46 + .../dal/model/response/SystemUserVo.java | 87 ++ .../system/dal/mysql/SystemDeptMapper.java | 37 + .../dal/mysql/SystemDictDetailMapper.java | 39 + .../system/dal/mysql/SystemDictMapper.java | 36 + .../dal/mysql/SystemEmailConfigMapper.java | 30 + .../system/dal/mysql/SystemJobMapper.java | 30 + .../dal/mysql/SystemLocalStorageMapper.java | 30 + .../system/dal/mysql/SystemMenuMapper.java | 34 + .../dal/mysql/SystemOperationLogMapper.java | 25 + .../dal/mysql/SystemOssStorageMapper.java | 33 + .../dal/mysql/SystemQuartzJobMapper.java | 30 + .../dal/mysql/SystemQuartzLogMapper.java | 30 + .../dal/mysql/SystemRoleDeptMapper.java | 34 + .../system/dal/mysql/SystemRoleMapper.java | 33 + .../dal/mysql/SystemRoleMenuMapper.java | 39 + .../system/dal/mysql/SystemUserJobMapper.java | 34 + .../system/dal/mysql/SystemUserMapper.java | 56 ++ .../dal/mysql/SystemUserRoleMapper.java | 33 + .../system/dal/redis/SystemUserInfoDAO.java | 76 ++ .../dal/redis/SystemUserOnlineInfoDAO.java | 188 ++++ .../features/CuteDynamicTableDemoApi.java | 98 ++ .../model/SystemMenuDynamicTableModel.java | 58 ++ .../system/framework/bizlock/BizLock.java | 62 ++ .../framework/bizlock/BizLockAspect.java | 87 ++ .../mybatisplus/MpMetaObjectHandler.java | 48 + .../framework/operalog/OperationLog.java | 38 + .../operalog/OperationLogAspect.java | 128 +++ .../permission/config/AuthorityConfig.java | 41 + .../config/SpringSecurityConfig.java | 125 +++ .../permission/core/KitSecurityHelper.java | 151 +++ .../core/handler/JwtAccessDeniedHandler.java | 43 + .../handler/JwtAuthenticationEntryPoint.java | 46 + .../permission/core/handler/TokenFilter.java | 88 ++ .../core/handler/TokenProvider.java | 167 ++++ .../core/handler/UserDetailsHandler.java | 69 ++ .../framework/quartz/ActiveJobRegister.java | 53 ++ .../framework/quartz/ExecutionJobBean.java | 137 +++ .../system/framework/quartz/QuartzManage.java | 192 ++++ .../storage/OssStorageConfiguration.java | 45 + .../storage/local/MultipartConfig.java | 56 ++ .../storage/minio/MinioRepository.java | 372 ++++++++ .../java/cn/odboy/system/job/TestTask.java | 36 + .../system/openapi/OpenCaptchaController.java | 42 + .../system/service/SystemAuthService.java | 120 +++ .../system/service/SystemCaptchaService.java | 79 ++ .../system/service/SystemDataService.java | 96 ++ .../system/service/SystemDeptService.java | 422 +++++++++ .../service/SystemDictDetailService.java | 149 +++ .../system/service/SystemDictService.java | 166 ++++ .../system/service/SystemEmailService.java | 157 ++++ .../system/service/SystemJobService.java | 158 ++++ .../service/SystemLocalStorageService.java | 194 ++++ .../system/service/SystemMenuService.java | 581 ++++++++++++ .../system/service/SystemMonitorService.java | 217 +++++ .../service/SystemOperationLogService.java | 44 + .../service/SystemOssStorageService.java | 77 ++ .../service/SystemQuartzJobService.java | 335 +++++++ .../system/service/SystemRoleDeptService.java | 94 ++ .../system/service/SystemRoleMenuService.java | 136 +++ .../system/service/SystemRoleService.java | 301 ++++++ .../system/service/SystemUserDeptService.java | 39 + .../system/service/SystemUserJobService.java | 89 ++ .../system/service/SystemUserRoleService.java | 196 ++++ .../system/service/SystemUserService.java | 527 +++++++++++ .../impl/SystemMinioStorageServiceImpl.java | 172 ++++ .../java/cn/odboy/system/util/KitSqlUtil.java | 189 ++++ .../META-INF/resources/druid/index.html | 13 + .../mapper/SystemDictDetailMapper.xml | 29 + .../resources/mapper/SystemRoleDeptMapper.xml | 10 + .../resources/mapper/SystemRoleMapper.xml | 13 + .../resources/mapper/SystemRoleMenuMapper.xml | 40 + .../resources/mapper/SystemUserJobMapper.xml | 11 + .../resources/mapper/SystemUserMapper.xml | 15 + .../resources/mapper/SystemUserRoleMapper.xml | 22 + .../template/SystemEmailCaptchaTemplate.ftl | 49 + .../SystemQuartzJobTaskAlarmTemplate.ftl | 69 ++ cutejava-springboot4/cutejava-starter/pom.xml | 41 + .../src/main/java/cn/odboy/AppRun.java | 33 + .../src/main/java/cn/odboy/GenCode.java | 47 + .../java/cn/odboy/HealthCheckPointer.java | 46 + .../src/main/resources/application-dev.yml | 156 ++++ .../src/main/resources/application-prod.yml | 167 ++++ .../src/main/resources/application-quartz.yml | 50 + .../src/main/resources/application.yml | 6 + .../src/main/resources/banner.txt | 8 + .../src/main/resources/logback.xml | 43 + .../src/main/resources/spy.properties | 20 + .../java/cn/odboy/MyBatisMapperTests.java | 53 ++ .../java/cn/odboy/SystemApplicationTests.java | 65 ++ .../java/cn/odboy/util/KitDateUtilTest.java | 31 + .../cn/odboy/util/KitDesEncryptUtilTest.java | 56 ++ .../java/cn/odboy/util/KitFileUtilTest.java | 55 ++ .../java/cn/odboy/util/KitStringUtilTest.java | 61 ++ .../java/cn/odboy/util/WorkTimeTests.java | 30 + cutejava-springboot4/pom.xml | 405 ++++++++ 304 files changed, 23942 insertions(+) create mode 100644 cutejava-springboot4/.gitignore create mode 100644 cutejava-springboot4/LICENSE create mode 100644 cutejava-springboot4/cutejava-framework/pom.xml create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousAccess.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousDeleteMapping.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousGetMapping.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPatchMapping.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPostMapping.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPutMapping.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitArgs.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseCreateTimeTb.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseTimeTb.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUpdateTimeTb.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUserCreateTimeTb.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUserTimeLogicTb.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUserTimeTb.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitObject.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitPageArgs.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitPageResult.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitSelectOptionVo.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/CaptchaCodeEnum.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/FileTypeEnum.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/RequestMethodEnum.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/SystemConst.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableColumn.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableResponse.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/BeanLoadFinishListener.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/BootApplication.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/KitRequestHolder.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/KitScriptHelper.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/KitSpringBeanHolder.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/BadRequestException.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/ServerException.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/ApiError.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/GlobalExceptionHandler.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeLog.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeLogAspect.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/monitor/controller/HealthCheckController.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/monitor/service/KitHealthCheckPointService.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/DataTypeEnum.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/KitMpQTypeEnum.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/KitMpQuery.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpCustomP6SpyLogger.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpInsertBatchSqlInjector.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MybatisPlusConfig.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/KitMpCmdGenUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/KitMpQUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/KitMpMapper.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/KitMpService.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/KitMpServiceImpl.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/AppProperties.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/CaptchaModel.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ContentRsaEncodeSettingModel.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/JwtAuthSettingModel.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/OSSConfigModel.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/StorageOSSModel.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/SwaggerApiDocSettingModel.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ThreadPoolSettingModel.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginCaptchaSettingModel.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginSettingModel.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/quartz/config/QuartzConfig.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/quartz/core/QuartzRunnable.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/DemoMessageSubscriber.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/KitRedisHelper.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/RedisConfiguration.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/RedisMessageConfig.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/config/AsyncTheadPoolConfig.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringArraysToListDateConverter.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringToDateConverter.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/core/ConfigurerAdapter.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/core/KitFileLocalUploadHelper.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/KitWsBizCodeConst.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/KitWsMessagePublisher.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/WsMessageSubscriber.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/config/WebSocketEndpointConfig.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/KitWsClientManager.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/KitWsMessage.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/KitWsServer.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/model/WsSidVo.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/util/WsMessageUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitAnonTagUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBase64SecretUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBeanUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBigDecimalUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBrowserUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitClassUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitCloseUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitCollUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitDateUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitDesEncryptUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitDistributedIdUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitFileUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitIPUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitJsonUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitListUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitLogFmtUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitPageUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitResourceTemplateUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitReturnValueHandleUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitRsaEncryptUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitSystemUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitThreadUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitValidUtil.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitExcelExporter.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitExcelPageWriteHelper.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitXlsxExportDataTransferRowHandler.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitXlsxExportUtil.java create mode 100644 cutejava-springboot4/cutejava-module-system/pom.xml create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteDeptTreeApi.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteFileUploadApi.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteProductLineSelectApi.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteProductLineSelectProApi.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteUserSelectApi.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteDeptTreeVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteProductLineSelectProVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteProductLineSelectVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteUserSelectVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteBaseService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteDeptTreeService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteFileUploadService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteProductLineSelectProService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteProductLineSelectService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteUserSelectService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemCacheKey.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemCaptchaBizEnum.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemDataScopeEnum.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemTransferProtocolConst.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemYesOrNoChConst.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemZhConst.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemAuthController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemCaptchaController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDeptController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictDetailController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemEmailController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemJobController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemLocalStorageController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMenuController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMonitorController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOnlineController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOperationLogController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOssStorageController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemQuartzJobController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemRoleController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemUserController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDeptTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictDetailTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemEmailConfigTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemJobTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemLocalStorageTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemMenuTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOperationLogTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOssStorageTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzJobTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzLogTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleDeptTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleMenuTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserJobTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserRoleTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserTb.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemDeptExportRowVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemDictExportRowVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemJobExportRowVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemLocalStorageExportRowVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemMenuExportRowVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemOssStorageExportRowVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemQuartzJobExportRowVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemQuartzLogExportRowVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemRoleExportRowVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemUserExportRowVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemUserOnlineExportRowVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCheckEmailCaptchaArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateDeptArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateDictArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateDictDetailArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateJobArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateRoleArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryDeptArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryDictArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryDictDetailArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryJobArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryMenuArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryOperationLogArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryQuartzJobArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryRoleArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryStorageArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryUserArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemSendEmailArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemUpdateQuartzJobArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemUpdateUserPasswordArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemUserLoginArgs.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemAuthVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemCaptchaVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemDeptVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemDictDetailVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemMenuMetaVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemMenuRouterVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemMenuVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemOssStorageVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemRoleCodeVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemRoleVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemSimpleUserVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserInfoVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserJwtVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserOnlineVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserVo.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDeptMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictDetailMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemEmailConfigMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemJobMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemLocalStorageMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemMenuMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOperationLogMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOssStorageMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzJobMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzLogMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleDeptMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMenuMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserJobMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserRoleMapper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserInfoDAO.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserOnlineInfoDAO.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/features/model/SystemMenuDynamicTableModel.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLock.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLockAspect.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/mybatisplus/MpMetaObjectHandler.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLog.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLogAspect.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/AuthorityConfig.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/SpringSecurityConfig.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/KitSecurityHelper.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAccessDeniedHandler.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAuthenticationEntryPoint.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenFilter.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenProvider.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/UserDetailsHandler.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ActiveJobRegister.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ExecutionJobBean.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/QuartzManage.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/OssStorageConfiguration.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/local/MultipartConfig.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/minio/MinioRepository.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/job/TestTask.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/openapi/OpenCaptchaController.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemAuthService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemCaptchaService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDataService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDeptService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictDetailService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemEmailService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemJobService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemLocalStorageService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMenuService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMonitorService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemOperationLogService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemOssStorageService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemQuartzJobService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleDeptService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleMenuService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserDeptService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserJobService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserRoleService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserService.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/impl/SystemMinioStorageServiceImpl.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/util/KitSqlUtil.java create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/resources/META-INF/resources/druid/index.html create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemDictDetailMapper.xml create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemRoleDeptMapper.xml create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemRoleMapper.xml create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemRoleMenuMapper.xml create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemUserJobMapper.xml create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemUserMapper.xml create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemUserRoleMapper.xml create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/resources/template/SystemEmailCaptchaTemplate.ftl create mode 100644 cutejava-springboot4/cutejava-module-system/src/main/resources/template/SystemQuartzJobTaskAlarmTemplate.ftl create mode 100644 cutejava-springboot4/cutejava-starter/pom.xml create mode 100644 cutejava-springboot4/cutejava-starter/src/main/java/cn/odboy/AppRun.java create mode 100644 cutejava-springboot4/cutejava-starter/src/main/java/cn/odboy/GenCode.java create mode 100644 cutejava-springboot4/cutejava-starter/src/main/java/cn/odboy/HealthCheckPointer.java create mode 100644 cutejava-springboot4/cutejava-starter/src/main/resources/application-dev.yml create mode 100644 cutejava-springboot4/cutejava-starter/src/main/resources/application-prod.yml create mode 100644 cutejava-springboot4/cutejava-starter/src/main/resources/application-quartz.yml create mode 100644 cutejava-springboot4/cutejava-starter/src/main/resources/application.yml create mode 100644 cutejava-springboot4/cutejava-starter/src/main/resources/banner.txt create mode 100644 cutejava-springboot4/cutejava-starter/src/main/resources/logback.xml create mode 100644 cutejava-springboot4/cutejava-starter/src/main/resources/spy.properties create mode 100644 cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/MyBatisMapperTests.java create mode 100644 cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/SystemApplicationTests.java create mode 100644 cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitDateUtilTest.java create mode 100644 cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitDesEncryptUtilTest.java create mode 100644 cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitFileUtilTest.java create mode 100644 cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitStringUtilTest.java create mode 100644 cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/WorkTimeTests.java create mode 100644 cutejava-springboot4/pom.xml diff --git a/cutejava-springboot4/.gitignore b/cutejava-springboot4/.gitignore new file mode 100644 index 00000000..a4dad206 --- /dev/null +++ b/cutejava-springboot4/.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-springboot4/LICENSE b/cutejava-springboot4/LICENSE new file mode 100644 index 00000000..78a36cb2 --- /dev/null +++ b/cutejava-springboot4/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-springboot4/cutejava-framework/pom.xml b/cutejava-springboot4/cutejava-framework/pom.xml new file mode 100644 index 00000000..3fe35a9c --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/pom.xml @@ -0,0 +1,12 @@ + + + + cutejava-springboot4 + cn.odboy + 1.5.2 + + 4.0.0 + cutejava-framework + 公共模块 + \ No newline at end of file diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousAccess.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousAccess.java new file mode 100644 index 00000000..a85c6e53 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousAccess.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2026 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 用于标记匿名访问方法 + */ +@Inherited +@Documented +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AnonymousAccess { + +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousDeleteMapping.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousDeleteMapping.java new file mode 100644 index 00000000..cb9d7ce8 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousDeleteMapping.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2026 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Annotation for mapping HTTP {@code DELETE} requests onto specific handler methods. 支持匿名访问 DeleteMapping + * + * @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-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousGetMapping.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousGetMapping.java new file mode 100644 index 00000000..8e10707c --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousGetMapping.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2026 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Annotation for mapping HTTP {@code GET} requests onto specific handler methods. + *

+ * 支持匿名访问 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-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPatchMapping.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPatchMapping.java new file mode 100644 index 00000000..c3e6ed0f --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPatchMapping.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2026 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Annotation for mapping HTTP {@code PATCH} requests onto specific handler methods. 支持匿名访问 PatchMapping + * + * @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-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPostMapping.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPostMapping.java new file mode 100644 index 00000000..7a43f104 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPostMapping.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2026 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Annotation for mapping HTTP {@code POST} requests onto specific handler methods. 支持匿名访问 PostMapping + * + * @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-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPutMapping.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPutMapping.java new file mode 100644 index 00000000..b5f185ca --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/annotation/AnonymousPutMapping.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2026 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Annotation for mapping HTTP {@code PUT} requests onto specific handler methods. 支持匿名访问 PutMapping + * + * @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-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitArgs.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitArgs.java new file mode 100644 index 00000000..c51cf1c3 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitArgs.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2026 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 jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +/** + * 通用参数 + * + * @author odboy + * @date 2025-07-23 + */ +public class KitArgs { + + @Getter + @Setter + public static class FindByLongId extends KitObject { + + @NotNull(message = "id必填") + private Long id; + } + + @Getter + @Setter + public static class DeleteById extends KitObject { + + @NotNull(message = "id必填") + private Long id; + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseCreateTimeTb.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseCreateTimeTb.java new file mode 100644 index 00000000..e1a1bed9 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseCreateTimeTb.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2026 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 java.util.Date; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +public class KitBaseCreateTimeTb extends KitObject { + + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true) + private Date createTime; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseTimeTb.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseTimeTb.java new file mode 100644 index 00000000..7d5d428d --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseTimeTb.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2026 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 java.util.Date; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +public class KitBaseTimeTb extends KitObject { + + @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-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUpdateTimeTb.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUpdateTimeTb.java new file mode 100644 index 00000000..0ba57fa1 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUpdateTimeTb.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2026 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 java.util.Date; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +public class KitBaseUpdateTimeTb extends KitObject { + + @TableField(fill = FieldFill.INSERT_UPDATE) + @ApiModelProperty(value = "更新时间: yyyy-MM-dd HH:mm:ss", hidden = true) + private Date updateTime; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUserCreateTimeTb.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUserCreateTimeTb.java new file mode 100644 index 00000000..c03a1e33 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUserCreateTimeTb.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2026 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 java.lang.reflect.Field; +import java.util.Date; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.springframework.data.annotation.CreatedBy; + +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +public class KitBaseUserCreateTimeTb extends KitObject { + + @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; + + @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(); + } + + /* 分组校验 */ + public @interface Create { + + } + + /* 分组校验 */ + public @interface Update { + + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUserTimeLogicTb.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUserTimeLogicTb.java new file mode 100644 index 00000000..9d1c99de --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUserTimeLogicTb.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2026 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 java.util.Date; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.LastModifiedBy; + +/** + * 逻辑删除通用模型 + * + * @author odboy + * @date 2025-01-12 + */ +@Getter +@Setter +public class KitBaseUserTimeLogicTb extends KitObject { + + @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-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUserTimeTb.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUserTimeTb.java new file mode 100644 index 00000000..96652bc3 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitBaseUserTimeTb.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2026 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 java.lang.reflect.Field; +import java.util.Date; +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; + +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +public class KitBaseUserTimeTb extends KitObject { + + @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; + + @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(); + } + + /* 分组校验 */ + public @interface Create { + + } + + /* 分组校验 */ + public @interface Update { + + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitObject.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitObject.java new file mode 100644 index 00000000..615cf630 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitObject.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021-2026 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 KitObject implements Serializable { + + private static final long serialVersionUID = 1L; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitPageArgs.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitPageArgs.java new file mode 100644 index 00000000..572b5189 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitPageArgs.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021-2026 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 cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import io.swagger.annotations.ApiModelProperty; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +/** + * 数据分页参数 + * + * @author odboy + * @date 2025-01-20 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class KitPageArgs extends KitObject { + + @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; + @ApiModelProperty(value = "排序参数") + private OrderBy orderBy; + + @Setter + public static class OrderBy extends KitObject { + + /** + * 排序列名称 + */ + private String column; + /** + * 排序方式 + */ + private String orderType; + + /** + * 字段属性转下划线 + * + * @return / + */ + public String getColumn() { + return StrUtil.toUnderlineCase(this.column); + } + + /** + * 是否顺序排序 + * + * @return / + */ + public boolean isAsc() { + return "asc".equals(this.orderType); + } + + /** + * 绑定 QueryWrapper,应用排序条件 + * + * @param wrapper QueryWrapper + */ + public void bindWrapper(QueryWrapper wrapper) { + wrapper.orderBy(true, this.isAsc(), this.getColumn()); + } + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitPageResult.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitPageResult.java new file mode 100644 index 00000000..e40fda1c --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitPageResult.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2026 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 java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 结果封装类 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class KitPageResult extends KitObject { + + private List content; + private long totalElements; + + public static KitPageResult emptyListData() { + KitPageResult result = new KitPageResult<>(); + result.setTotalElements(0); + result.setContent(new ArrayList<>()); + return result; + } + + public static KitPageResult listData(IPage page) { + KitPageResult result = new KitPageResult<>(); + result.setTotalElements(page.getTotal()); + result.setContent(page.getRecords()); + return result; + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitSelectOptionVo.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitSelectOptionVo.java new file mode 100644 index 00000000..2b712e9a --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/base/KitSelectOptionVo.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2026 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.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * 元数据选项 + * + * @author odboy + * @date 2025-01-13 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class KitSelectOptionVo extends KitObject { + + private String label; + private String value; + private Map ext; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/CaptchaCodeEnum.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/CaptchaCodeEnum.java new file mode 100644 index 00000000..2e9e1e51 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/CaptchaCodeEnum.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/FileTypeEnum.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/FileTypeEnum.java new file mode 100644 index 00000000..fd95343e --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/FileTypeEnum.java @@ -0,0 +1,28 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/RequestMethodEnum.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/RequestMethodEnum.java new file mode 100644 index 00000000..e4bf5b10 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/RequestMethodEnum.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/SystemConst.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/SystemConst.java new file mode 100644 index 00000000..8e79d47c --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constant/SystemConst.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2026 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"; + String CURRENT_APP_NAME = "cutejava"; + String CURRENT_APP_TITLE = "CuteJava"; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableColumn.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableColumn.java new file mode 100644 index 00000000..5f4e506d --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableColumn.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2026 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.feature; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) +public @interface KitDynamicTableColumn { + + /** + * 标题 + */ + String value() default ""; + + /** + * 宽度 + */ + int width() default 0; + + /** + * 是否排序字段 + */ + boolean sortable() default false; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableResponse.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableResponse.java new file mode 100644 index 00000000..75081f8d --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableResponse.java @@ -0,0 +1,172 @@ +/* + * Copyright 2021-2026 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.feature; + +import cn.hutool.core.util.ReflectUtil; +import cn.odboy.base.KitObject; +import cn.odboy.base.KitPageArgs; +import cn.odboy.util.KitBeanUtil; +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.plugins.pagination.Page; +import lombok.Getter; +import lombok.Setter; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/** + * 动态表格 + * + * @param 数据表对象 + * @param 渲染对象 + * @author odboy + * @date 2026-02-05 + */ +@Getter +public class KitDynamicTableResponse extends KitObject { + + /** + * 主键 + */ + @Setter + private String primaryKey = "id"; + /** + * 表格列定义 + */ + private List columns = new ArrayList<>(); + /** + * 数据总数 + */ + private Long totalElements; + /** + * 数据内容 + */ + private List content; + + /** + * @param clazz 渲染类型 + * @param pageResult 渲染类型分页参数 + */ + public KitDynamicTableResponse(Class clazz, IPage pageResult) { + this.renderColumns(clazz); + this.setContent(clazz, pageResult); + } + + /** + * @param clazz 渲染类型 + * @param pageResult 渲染类型分页参数 + * @param primaryKey 主键 + */ + public KitDynamicTableResponse(Class clazz, IPage pageResult, String primaryKey) { + this.primaryKey = primaryKey; + this.renderColumns(clazz); + this.setContent(clazz, pageResult); + } + + /** + * @param clazz 渲染类型 + * @param pageArgs 分页参数 + * @param baseMapper 数据表Mapper + * @param wrapper 查询条件 + */ + public KitDynamicTableResponse(Class clazz, KitPageArgs pageArgs, BaseMapper baseMapper, LambdaQueryWrapper wrapper) { + this.renderColumns(clazz); + Page pageResult = baseMapper.selectPage(new Page<>(pageArgs.getPage(), pageArgs.getSize()), wrapper); + this.setContent(clazz, pageResult); + } + + /** + * @param clazz 渲染类型 + * @param pageArgs 分页参数 + * @param primaryKey 主键 + * @param baseMapper 数据表Mapper + * @param wrapper 查询条件 + */ + public KitDynamicTableResponse(Class clazz, KitPageArgs pageArgs, String primaryKey, BaseMapper baseMapper, LambdaQueryWrapper wrapper) { + this.primaryKey = primaryKey; + this.renderColumns(clazz); + Page pageResult = baseMapper.selectPage(new Page<>(pageArgs.getPage(), pageArgs.getSize()), wrapper); + this.setContent(clazz, pageResult); + } + + /** + * 渲染表格列 + * + * @param clazz 渲染类型 + */ + private void renderColumns(Class clazz) { + List columns = new ArrayList<>(); + if (clazz == null) { + this.columns = columns; + return; + } + Field[] fields = ReflectUtil.getFields(clazz); + for (Field field : fields) { + if ("serialVersionUID".equals(field.getName())) { + continue; + } + Column column = new Column(); + column.setName(field.getName()); + KitDynamicTableColumn annotation = field.getAnnotation(KitDynamicTableColumn.class); + if (annotation == null) { + column.setTitle(field.getName()); + column.setWidth(null); + } else { + column.setTitle(annotation.value()); + column.setWidth(annotation.width() == 0 ? null : annotation.width()); + column.setSortable(annotation.sortable()); + } + columns.add(column); + } + this.columns = columns; + } + + /** + * @param clazz 渲染类型 + * @param pageResult 渲染结果 + */ + public void setContent(Class clazz, IPage pageResult) { + if (pageResult == null) { + return; + } + this.totalElements = pageResult.getTotal(); + this.content = KitBeanUtil.copyToList(pageResult.getRecords(), clazz); + } + + @Getter + @Setter + public static class Column { + + /** + * 索引名称 + */ + private String name; + /** + * 索引标题 + */ + private String title; + /** + * 表格宽度 + */ + private Integer width; + /** + * 是否排序列 + */ + private Boolean sortable = false; + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java new file mode 100644 index 00000000..9f33d1d4 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2026 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.feature; + +import cn.odboy.base.KitPageArgs; +import cn.odboy.framework.mybatisplus.core.KitMpQUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.experimental.UtilityClass; + +/** + * 动态表格辅助工具 + * + * @author odboy + */ +@UtilityClass +public class KitDynamicTableUtil { + + /** + * 渲染排序规则 + * + * @param pageArgs 分页参数 + * @param 数据表对象 + * @param 渲染对象 + * @return / + */ + public static QueryWrapper renderOrder(KitPageArgs pageArgs) { + QueryWrapper wrapper = new QueryWrapper<>(); + KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy(); + if (orderBy != null) { + wrapper.orderBy(true, orderBy.isAsc(), orderBy.getColumn()); + } + return wrapper; + } + + /** + * 渲染所有规则(需要配合 @KitMpQuery 注解使用) + * + * @param pageArgs 分页参数 + * @param 数据表对象 + * @param 渲染对象 + * @return / + */ + public static LambdaQueryWrapper renderAll(KitPageArgs pageArgs) { + QueryWrapper wrapper = KitMpQUtil.build(pageArgs.getArgs()); + KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy(); + if (orderBy != null) { + wrapper.orderBy(true, orderBy.isAsc(), orderBy.getColumn()); + } + return wrapper.lambda(); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/BeanLoadFinishListener.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/BeanLoadFinishListener.java new file mode 100644 index 00000000..969a19e3 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/BeanLoadFinishListener.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2026 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都加载完毕了"); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/BootApplication.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/BootApplication.java new file mode 100644 index 00000000..91c75738 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/BootApplication.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021-2026 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 java.net.InetAddress; +import java.net.UnknownHostException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; + +/** + * 主要目的是输出应用的服务地址 + * + * @author odboy + * @date 2024-04-20 + */ +@Slf4j +public class BootApplication { + + 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(""" + ---------------------------------------------------------- + \tApplication is running! Access URLs: + \tLocal: \t\thttp://localhost:{}{} + \tExternal: \thttp://{}:{}{} + \tSwagger文档: \thttp://{}:{}{}/doc.html + ----------------------------------------------------------""", port, path, ip, port, path, ip, port, path); + } + + @Bean + public KitSpringBeanHolder springBeanHolder() { + return new KitSpringBeanHolder(); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/KitRequestHolder.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/KitRequestHolder.java new file mode 100644 index 00000000..503586d9 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/KitRequestHolder.java @@ -0,0 +1,34 @@ + +/* + * Copyright 2021-2026 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 jakarta.servlet.http.HttpServletRequest; +import java.util.Objects; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * 获取 HttpServletRequest + */ +public class KitRequestHolder { + + public static HttpServletRequest getHttpServletRequest() { + return ((ServletRequestAttributes) Objects.requireNonNull( + RequestContextHolder.getRequestAttributes())).getRequest(); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/KitScriptHelper.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/KitScriptHelper.java new file mode 100644 index 00000000..9bd822eb --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/KitScriptHelper.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2026 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 com.ezylang.evalex.EvaluationException; +import com.ezylang.evalex.Expression; +import com.ezylang.evalex.data.EvaluationValue; +import com.ezylang.evalex.parser.ParseException; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 脚本工具 + * + * @author odboy + * @date 2025-09-23 + */ +@Slf4j +@Component +public class KitScriptHelper { + + public Object evalObj(String script, Map args) throws EvaluationException, ParseException { + Expression expression = new Expression(script); + expression.withValues(args); + EvaluationValue result = expression.evaluate(); + return result.getValue(); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/KitSpringBeanHolder.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/KitSpringBeanHolder.java new file mode 100644 index 00000000..a0ccef14 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/context/KitSpringBeanHolder.java @@ -0,0 +1,169 @@ +/* + * Copyright 2021-2026 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 jakarta.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +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; + +@Slf4j +public class KitSpringBeanHolder implements ApplicationContextAware, DisposableBean { + + private static final List CALL_BACKS = new ArrayList<>(); + private static ApplicationContext applicationContext = null; + private static boolean addCallback = true; + + /** + * 针对 某些初始化方法, 在SpringContextHolder 未初始化时 提交回调方法。 在SpringContextHolder 初始化后, 进行回调使用 + * + * @param callBack 回调函数 + */ + public synchronized static void addCallBacks(CallBack callBack) { + if (addCallback) { + CALL_BACKS.add(callBack); + } else { + log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName()); + callBack.executor(); + } + } + + /** + * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. + */ + @SuppressWarnings("all") + 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; + } + + /** + * 获取 @Service 的所有 bean 名称 + * + * @return / + */ + public static List getAllServiceBeanName() { + return new ArrayList<>(Arrays.asList(applicationContext.getBeanNamesForAnnotation(Service.class))); + } + + @Override + public void destroy() { + KitSpringBeanHolder.clearHolder(); + } + + @Override + public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException { + if (KitSpringBeanHolder.applicationContext != null) { + log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:{}", KitSpringBeanHolder.applicationContext); + } + KitSpringBeanHolder.applicationContext = applicationContext; + if (addCallback) { + for (CallBack callBack : KitSpringBeanHolder.CALL_BACKS) { + callBack.executor(); + } + CALL_BACKS.clear(); + } + KitSpringBeanHolder.addCallback = false; + } + + public interface CallBack { + + /** + * 回调执行方法 + */ + void executor(); + + /** + * 本回调任务名称 + * + * @return / + */ + default String getCallBackName() { + return Thread.currentThread().threadId() + ":" + this.getClass().getName(); + } + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/BadRequestException.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/BadRequestException.java new file mode 100644 index 00000000..186d739e --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/BadRequestException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/ServerException.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/ServerException.java new file mode 100644 index 00000000..d7304d59 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/ServerException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/ApiError.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/ApiError.java new file mode 100644 index 00000000..e043bbcf --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/ApiError.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/GlobalExceptionHandler.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/GlobalExceptionHandler.java new file mode 100644 index 00000000..0326c129 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,102 @@ +/* + * Copyright 2021-2026 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.odboy.framework.exception.BadRequestException; +import io.swagger.v3.oas.annotations.Hidden; +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 +@Hidden +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * 处理所有不可知的异常 + */ + @ExceptionHandler(Throwable.class) + public ResponseEntity handleThrowable(Throwable e) { + // 打印堆栈信息 + log.error("handleThrowable", e); + return this.buildResponseEntity(ApiError.error(e.getMessage())); + } + + /** + * 处理所有不可知的异常 + */ + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + // 打印堆栈信息 + log.error("handleException", e); + return this.buildResponseEntity(ApiError.error(e.getMessage())); + } + + /** + * BadCredentialsException + */ + @ExceptionHandler(BadCredentialsException.class) + public ResponseEntity handleBadCredentialsException(BadCredentialsException e) { + // 打印堆栈信息 + String message = "坏的凭证".equals(e.getMessage()) ? "用户名或密码不正确" : e.getMessage(); + log.error(message); + return this.buildResponseEntity(ApiError.error(message)); + } + + /** + * 处理自定义异常 + */ + @ExceptionHandler(value = BadRequestException.class) + public ResponseEntity handleBadRequestException(BadRequestException e) { + // 打印堆栈信息 + log.error("handleBadRequestException", e); + return this.buildResponseEntity(ApiError.error(e.getStatus(), e.getMessage())); + } + + /** + * 处理所有接口数据验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + // 打印堆栈信息 + log.error("handleMethodArgumentNotValidException", e); + ObjectError objectError = e.getBindingResult().getAllErrors().get(0); + String message = objectError.getDefaultMessage(); + if (objectError instanceof FieldError) { + message = ((FieldError) objectError).getField() + ": " + message; + } + return this.buildResponseEntity(ApiError.error(message)); + } + + /** + * 统一返回 + */ + private ResponseEntity buildResponseEntity(ApiError apiError) { + return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getStatus())); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeLog.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeLog.java new file mode 100644 index 00000000..159e962f --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeLog.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2026 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 统计执行时长(仅对代理方法有效) + * + * @author odboy + */ +@Inherited +@Documented +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExecutionTimeLog { + + /** + * 接口描述 + */ + String description(); + + /** + * 是否展示异常日志 + */ + boolean showErrLog() default true; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeLogAspect.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeLogAspect.java new file mode 100644 index 00000000..eb21c8fb --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/logging/ExecutionTimeLogAspect.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2026 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 ExecutionTimeLogAspect { + + @Around("@annotation(executionTimeLog)") + public Object aroundFace(ProceedingJoinPoint joinPoint, ExecutionTimeLog executionTimeLog) + throws Throwable { + TimeInterval timeInterval = new TimeInterval(); + try { + Object proceed = joinPoint.proceed(); + log.info("[{}] 接口执行成功,耗时: {} ms", executionTimeLog.description(), + timeInterval.intervalMs()); + return proceed; + } catch (Exception e) { + if (executionTimeLog.showErrLog()) { + log.error("[{}] 接口执行失败,耗时: {} ms", executionTimeLog.description(), + timeInterval.intervalMs(), e); + } + throw e; + } + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/monitor/controller/HealthCheckController.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/monitor/controller/HealthCheckController.java new file mode 100644 index 00000000..979db0e0 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/monitor/controller/HealthCheckController.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2026 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.KitHealthCheckPointService; +import io.swagger.v3.oas.annotations.tags.Tag; +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") +@Tag(name = "系统:健康检查接口") +public class HealthCheckController { + + @Autowired + private KitHealthCheckPointService kitHealthCheckPointService; + + /** + * 就绪检查 + */ + @AnonymousGetMapping(value = "/readiness") + public ResponseEntity doReadiness() { + return kitHealthCheckPointService.doReadiness(); + } + + /** + * 存活检查 + */ + @AnonymousGetMapping(value = "/liveness") + public ResponseEntity doLiveness() { + return kitHealthCheckPointService.doLiveness(); + } + + // /** + // * 访问首页提示 + // */ + // @AnonymousGetMapping("/") + // public String index() { + // return "success"; + // } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/monitor/service/KitHealthCheckPointService.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/monitor/service/KitHealthCheckPointService.java new file mode 100644 index 00000000..1cd421f6 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/monitor/service/KitHealthCheckPointService.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021-2026 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 KitHealthCheckPointService { + + /** + * 就绪检查 + */ + ResponseEntity doReadiness(); + + /** + * 存活检查 + */ + ResponseEntity doLiveness(); +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/DataTypeEnum.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/DataTypeEnum.java new file mode 100644 index 00000000..ea5f4855 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/DataTypeEnum.java @@ -0,0 +1,120 @@ +/* + * Copyright 2021-2026 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.Getter; +import lombok.extern.slf4j.Slf4j; + +@Getter +@Slf4j +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 static final String JDBC_URL_PREFIX = "jdbc:"; + private final String feature; + private final String desc; + private final String driver; + private final String keywordPrefix; + private final String keywordSuffix; + private final String aliasPrefix; + private final String aliasSuffix; + + 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.forName(dataTypeEnum.getDriver()); + } catch (ClassNotFoundException e) { + throw new BadRequestException("Unable to get driver instance: " + jdbcUrl); + } + return dataTypeEnum; + } + } + return null; + } + +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/KitMpQTypeEnum.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/KitMpQTypeEnum.java new file mode 100644 index 00000000..f015c01e --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/KitMpQTypeEnum.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2026 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 KitMpQTypeEnum { + /** + * 相等 + */ + 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-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/KitMpQuery.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/KitMpQuery.java new file mode 100644 index 00000000..e56ad5d3 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/KitMpQuery.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2026 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 KitMpQuery { + + /** + * 基本对象的属性名 + */ + String propName() default ""; + + /** + * 查询方式 + */ + KitMpQTypeEnum type() default KitMpQTypeEnum.EQUAL; + + /** + * 多字段模糊搜索, 仅支持String类型字段, 多个用逗号隔开, 如@MpQuery(blurry = "email,username") + */ + String blurry() default ""; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpCustomP6SpyLogger.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpCustomP6SpyLogger.java new file mode 100644 index 00000000..7374b47a --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpCustomP6SpyLogger.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2026 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); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpInsertBatchSqlInjector.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpInsertBatchSqlInjector.java new file mode 100644 index 00000000..3c5fe44d --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MpInsertBatchSqlInjector.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2026 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 java.util.List; +import org.apache.ibatis.session.Configuration; + +/** + * 批量插入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; + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MybatisPlusConfig.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MybatisPlusConfig.java new file mode 100644 index 00000000..8a43e558 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/config/MybatisPlusConfig.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/KitMpCmdGenUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/KitMpCmdGenUtil.java new file mode 100644 index 00000000..3e219e93 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/KitMpCmdGenUtil.java @@ -0,0 +1,213 @@ +/* + * Copyright 2021-2026 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.KitBaseUserTimeTb; +import cn.odboy.framework.exception.BadRequestException; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.generator.FastAutoGenerator; +import com.baomidou.mybatisplus.generator.config.DataSourceConfig; +import com.baomidou.mybatisplus.generator.config.GlobalConfig; +import com.baomidou.mybatisplus.generator.config.IDbQuery; +import com.baomidou.mybatisplus.generator.config.OutputFile; +import com.baomidou.mybatisplus.generator.config.PackageConfig; +import com.baomidou.mybatisplus.generator.config.StrategyConfig; +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 java.sql.Types; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import lombok.Data; +import org.apache.ibatis.annotations.Mapper; + +/** + * 代码生成器入口 + * + * @author odboy + * @date 2025-01-11 + */ +@Data +public class KitMpCmdGenUtil { + + // 项目根路径。生成结果 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; + + 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(KitBaseUserTimeTb.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 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); + }); + } + + /** + * 代码生成函数 + * + * @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 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)); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/KitMpQUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/KitMpQUtil.java new file mode 100644 index 00000000..96dbcc5b --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/KitMpQUtil.java @@ -0,0 +1,232 @@ +/* + * Copyright 2021-2026 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.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.KitBaseUserTimeTb; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.mybatisplus.config.KitMpQuery; +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 jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +/** + * 查询条件创建工具 + */ +@Slf4j +public class KitMpQUtil { + + 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); + KitMpQuery q = field.getAnnotation(KitMpQuery.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(KitMpQuery q, Field field) { + String attributeName = StrUtil.isBlank(q.propName()) ? field.getName() : q.propName(); + return StrUtil.toUnderlineCase(attributeName); + } + + private static void handleWrapper(KitMpQuery 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 (CollUtil.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 (CollUtil.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().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; + } + + 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()); + } + + @Data + @TableName("test_domain") + private static class TestDomain { + + @NotNull(groups = KitBaseUserTimeTb.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; + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/KitMpMapper.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/KitMpMapper.java new file mode 100644 index 00000000..8f916240 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/KitMpMapper.java @@ -0,0 +1,124 @@ +/* + * Copyright 2021-2026 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.collection.CollUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.KitPageResult; +import cn.odboy.util.KitBeanUtil; +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.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 公共抽象Mapper接口类 + * + * @author odboy + * @date 2022-04-03 + */ +public interface KitMpMapper 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 HashMap<>(1); + List list = this.selectList(wrapper); + if (CollUtil.isEmpty(list)) { + return new HashMap<>(1); + } + E tempObj = list.stream().findFirst().orElse(null); + if (tempObj == null) { + return new HashMap<>(1); + } + Field[] fields = ReflectUtil.getFields(tempObj.getClass()); + 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; + } + } + for (E e : list) { + String fieldKey = String.valueOf(ReflectUtil.getFieldValue(e, keyField)); + if (fieldKey != null) { + // 下划线转驼峰 + String camelFieldKey = StrUtil.toCamelCase(fieldKey); + Object fieldValue = ReflectUtil.getFieldValue(e, valueField); + result.putIfAbsent(camelFieldKey, fieldValue); + } + } + return result; + } + + /** + * 条件查询列表, 并返回期望的对象(已测试) + * + * @param wrapper LambdaQueryWrapper + * @param clazz 期望的对象类型 + * @return List + */ + default List queryFeatureClazz(LambdaQueryWrapper wrapper, Class clazz) { + List list = this.selectList(wrapper); + return KitBeanUtil.copyToList(list, clazz); + } + + /** + * 条件查询分页列表, 并返回期望的对象(已测试) + * + * @param wrapper LambdaQueryWrapper wrapper + * @param clazz 期望的对象类型 + * @param pageable 分页参数 + * @return IPage + */ + default KitPageResult searchFeatureClazz(LambdaQueryWrapper wrapper, Class clazz, Page pageable) { + IPage pageInfo = this.selectPage(pageable, wrapper); + return new KitPageResult<>(KitBeanUtil.copyToList(pageInfo.getRecords(), clazz), pageInfo.getTotal()); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/KitMpService.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/KitMpService.java new file mode 100644 index 00000000..b66ebe81 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/KitMpService.java @@ -0,0 +1,139 @@ +/* + * Copyright 2021-2026 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.KitPageResult; +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 KitMpService extends IService { + + /** + * 保存数据(已测试) + * + * @param resources 非Tb对象 + * @return 影响的行数 + */ + int saveFeatureClazz(G resources); + + /** + * 批量保存数据(已测试) + * + * @param resources 非Tb对象集合 + * @return 影响的行数 + */ + int batchSaveFeatureClazz(List resources); + + /** + * 根据id更新数据(已测试) + * + * @param resources 非Tb对象 + * @return 影响的行数 + */ + int updateFeatureClazzById(G resources); + + /** + * 根据id更新数据(已测试) + * + * @param resources 非Tb对象 + * @return 影响的行数 + */ + boolean batchUpdateFeatureClazzById(Collection resources, int batchSize); + + /** + * 通过id查询(已测试) + * + * @param id 数据id + * @param targetClazz 目标类型 + */ + G getFeatureClazzById(Serializable id, Class targetClazz); + + /** + * 根据条件查询一条数据(已测试) + * + * @param wrapper 查询条件 + * @param orderColumn 排序字段 + * @return / + */ + T getClazzByArgs(LambdaQueryWrapper wrapper, SFunction orderColumn); + + /** + * 根据条件查询一条目标类型的数据(已测试) + * + * @param wrapper 查询条件 + * @param orderColumn 排序字段 + * @param targetClazz 目标类型 + * @return / + */ + G getFeatureClazzByArgs(LambdaQueryWrapper wrapper, SFunction orderColumn, Class targetClazz); + + /** + * 根据id集合查询目标数据(已测试) + * + * @param ids id集合 + * @param targetClazz 目标类型 + * @return / + */ + List listFeatureClazzByIds(List ids, Class targetClazz); + + /** + * 根据条件查询目标数据(已测试) + * + * @param wrapper 查询条件 + * @param targetClazz 目标类型 + * @return / + */ + List queryFeatureClazzByArgs(LambdaQueryWrapper wrapper, Class targetClazz); + + /** + * 根据条件分页查询数据(已测试) + * + * @param wrapper 查询条件 + * @param pageable 分页参数 + * @return / + */ + KitPageResult searchClazzByArgs(LambdaQueryWrapper wrapper, IPage pageable); + + /** + * 根据条件分页查询目标数据(已测试) + * + * @param wrapper 查询条件 + * @param targetClazz 目标类型 + * @param pageable 分页参数 + * @return / + */ + KitPageResult searchFeatureClazzByArgs(LambdaQueryWrapper wrapper, Class targetClazz, IPage pageable); + + /** + * 根据条件更新数据(已测试) + * + * @param wrapper 查询条件与值 + * @return 影响的行数 + */ + int updateClazzByArgs(LambdaQueryWrapper wrapper); +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/KitMpServiceImpl.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/KitMpServiceImpl.java new file mode 100644 index 00000000..2477b167 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/mybatisplus/core/interfaces/KitMpServiceImpl.java @@ -0,0 +1,118 @@ +/* + * Copyright 2021-2026 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.KitPageResult; +import cn.odboy.util.KitBeanUtil; +import cn.odboy.util.KitValidUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * 公共抽象Mapper接口类 + * + * @author odboy + * @date 2022-04-03 + */ +public class KitMpServiceImpl, T> extends ServiceImpl { + + public int saveFeatureClazz(G resources) { + return getBaseMapper().insert(KitBeanUtil.copyToClass(resources, this.getEntityClass())); + } + + public int batchSaveFeatureClazz(List resources) { + return getBaseMapper().insert(KitBeanUtil.copyToList(resources, this.getEntityClass())).size(); + } + + public int updateFeatureClazzById(G resources) { + return getBaseMapper().updateById(KitBeanUtil.copyToClass(resources, this.getEntityClass())); + } + + public int batchUpdateFeatureClazzById(Collection resources, int batchSize) { + return getBaseMapper().updateById(KitBeanUtil.copyToList(resources, this.getEntityClass()), batchSize).size(); +// String sqlStatement = this.getSqlStatement(SqlMethod.UPDATE_BY_ID); +// return this.executeBatch( +// resources, batchSize, (sqlSession, entity) -> { +// MapperMethod.ParamMap param = new MapperMethod.ParamMap<>(); +// param.put("et", KitBeanUtil.copyToClass(entity, this.getEntityClass())); +// sqlSession.update(sqlStatement, param); +// } +// ); + } + + public G getFeatureClazzById(Serializable id, Class targetClazz) { + return KitBeanUtil.copyToClass(this.getById(id), targetClazz); + } + + public T getClazzByArgs(LambdaQueryWrapper wrapper, SFunction orderColumn) { + KitValidUtil.isNull(orderColumn, "排序列", "orderColumn"); + if (wrapper == null) { + wrapper = new LambdaQueryWrapper<>(); + } + wrapper.orderByDesc(orderColumn); + wrapper.last("limit 1"); + return baseMapper.selectOne(wrapper); + } + + public G getFeatureClazzByArgs(LambdaQueryWrapper wrapper, SFunction orderColumn, Class targetClazz) { + KitValidUtil.isNull(orderColumn, "排序列", "orderColumn"); + if (wrapper == null) { + wrapper = new LambdaQueryWrapper<>(); + } + wrapper.orderByDesc(orderColumn); + wrapper.last("limit 1"); + return KitBeanUtil.copyToClass(baseMapper.selectOne(wrapper), targetClazz); + } + + public List listFeatureClazzByIds(List ids, Class targetClazz) { + return KitBeanUtil.copyToList(this.listByIds(ids), targetClazz); + } + + public List queryFeatureClazzByArgs(LambdaQueryWrapper wrapper, Class targetClazz) { + if (wrapper == null) { + wrapper = new LambdaQueryWrapper<>(); + } + return KitBeanUtil.copyToList(baseMapper.selectList(wrapper), targetClazz); + } + + public KitPageResult searchClazzByArgs(LambdaQueryWrapper wrapper, IPage pageable) { + if (wrapper == null) { + wrapper = new LambdaQueryWrapper<>(); + } + IPage originPageData = baseMapper.selectPage(pageable, wrapper); + return new KitPageResult<>(originPageData.getRecords(), originPageData.getTotal()); + } + + public KitPageResult searchFeatureClazzByArgs(LambdaQueryWrapper wrapper, Class targetClazz, IPage pageable) { + if (wrapper == null) { + wrapper = new LambdaQueryWrapper<>(); + } + IPage originPageData = baseMapper.selectPage(pageable, wrapper); + return new KitPageResult<>(KitBeanUtil.copyToList(originPageData.getRecords(), targetClazz), originPageData.getTotal()); + } + + public int updateClazzByArgs(LambdaUpdateWrapper wrapper) { + KitValidUtil.isNull(wrapper, "查询条件与值", "wrapper"); + return baseMapper.update(null, wrapper); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/AppProperties.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/AppProperties.java new file mode 100644 index 00000000..38b95335 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/AppProperties.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021-2026 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.CaptchaModel; +import cn.odboy.framework.properties.model.ContentRsaEncodeSettingModel; +import cn.odboy.framework.properties.model.JwtAuthSettingModel; +import cn.odboy.framework.properties.model.StorageOSSModel; +import cn.odboy.framework.properties.model.SwaggerApiDocSettingModel; +import cn.odboy.framework.properties.model.ThreadPoolSettingModel; +import cn.odboy.framework.properties.model.UserLoginSettingModel; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import java.util.List; + +/** + * 应用配置 + * + * @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; + /** + * 自动识别json对象白名单配置(仅允许解析的包名, 范围越小越安全)
未配置可能导致, 登录失败, 反复登录等问题 + */ + private List serialWhiteList; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/CaptchaModel.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/CaptchaModel.java new file mode 100644 index 00000000..ae015b1d --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/CaptchaModel.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2026 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.KitObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CaptchaModel extends KitObject { + + /** + * 验证码有效时间, 单位: 秒 + */ + private Long expireTime; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ContentRsaEncodeSettingModel.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ContentRsaEncodeSettingModel.java new file mode 100644 index 00000000..a22c3dd9 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ContentRsaEncodeSettingModel.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2026 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.KitObject; +import lombok.Getter; +import lombok.Setter; + +/** + * 密码加密传输, 前端公钥加密,后端私钥解密 + */ +@Getter +@Setter +public class ContentRsaEncodeSettingModel extends KitObject { + + private String privateKey; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/JwtAuthSettingModel.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/JwtAuthSettingModel.java new file mode 100644 index 00000000..4388d431 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/JwtAuthSettingModel.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2026 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.KitObject; +import lombok.Getter; +import lombok.Setter; + +/** + * Jwt参数配置 + */ +@Getter +@Setter +public class JwtAuthSettingModel extends KitObject { + + /** + * 必须使用最少88位的Base64对该令牌进行编码 + */ + private String base64Secret; + /** + * 令牌过期时间 此处单位/毫秒 + */ + private Long tokenValidityInSeconds; + /** + * token 续期检查 + */ + private Long detect; + /** + * 续期时间 + */ + private Long renew; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/OSSConfigModel.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/OSSConfigModel.java new file mode 100644 index 00000000..41373388 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/OSSConfigModel.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2026 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.KitObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class OSSConfigModel extends KitObject { + + /** + * 服务地址 + */ + private String endpoint; + /** + * 存储桶名称 + */ + private String bucketName; + /** + * Access Key + */ + private String accessKey; + /** + * Secret Key + */ + private String secretKey; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/StorageOSSModel.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/StorageOSSModel.java new file mode 100644 index 00000000..0a2e4296 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/StorageOSSModel.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/SwaggerApiDocSettingModel.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/SwaggerApiDocSettingModel.java new file mode 100644 index 00000000..95006ae9 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/SwaggerApiDocSettingModel.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2026 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.KitObject; +import lombok.Getter; +import lombok.Setter; + +/** + * @author odboy + * @date 2025-04-13 + */ +@Getter +@Setter +public class SwaggerApiDocSettingModel extends KitObject { + + private Boolean enabled; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ThreadPoolSettingModel.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ThreadPoolSettingModel.java new file mode 100644 index 00000000..067a5833 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/ThreadPoolSettingModel.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2026 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.KitObject; +import lombok.Getter; +import lombok.Setter; + +/** + * 线程池 配置 + * + * @author odboy + * @date 2025-04-13 + */ +@Getter +@Setter +public class ThreadPoolSettingModel extends KitObject { + + /** + * 核心线程池大小 + */ + private int corePoolSize; + /** + * 最大线程数 + */ + private int maxPoolSize; + /** + * 活跃时间 + */ + private int keepAliveSeconds; + /** + * 队列容量 + */ + private int queueCapacity; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginCaptchaSettingModel.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginCaptchaSettingModel.java new file mode 100644 index 00000000..ad5fd417 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginCaptchaSettingModel.java @@ -0,0 +1,126 @@ +/* + * Copyright 2021-2026 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.KitObject; +import cn.odboy.constant.CaptchaCodeEnum; +import cn.odboy.framework.exception.BadRequestException; +import com.wf.captcha.ArithmeticCaptcha; +import com.wf.captcha.ChineseCaptcha; +import com.wf.captcha.ChineseGifCaptcha; +import com.wf.captcha.GifCaptcha; +import com.wf.captcha.SpecCaptcha; +import com.wf.captcha.base.Captcha; +import lombok.Getter; +import lombok.Setter; +import java.awt.*; + +/** + * 验证码配置 + * + * @author odboy + */ +@Getter +@Setter +public class UserLoginCaptchaSettingModel extends KitObject { + + 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-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginSettingModel.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginSettingModel.java new file mode 100644 index 00000000..5250e774 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/properties/model/UserLoginSettingModel.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2026 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.KitObject; +import lombok.Getter; +import lombok.Setter; + +/** + * 登录配置 + * + * @author odboy + */ +@Getter +@Setter +public class UserLoginSettingModel extends KitObject { + + /** + * 账号单用户 登录 + */ + private boolean single = false; + private UserLoginCaptchaSettingModel captchaSetting; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/quartz/config/QuartzConfig.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/quartz/config/QuartzConfig.java new file mode 100644 index 00000000..04d311f4 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/quartz/config/QuartzConfig.java @@ -0,0 +1,64 @@ +/* + * Copyright 2021-2026 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 jakarta.annotation.Nonnull; +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.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-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/quartz/core/QuartzRunnable.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/quartz/core/QuartzRunnable.java new file mode 100644 index 00000000..fb287ccf --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/quartz/core/QuartzRunnable.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021-2026 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.KitSpringBeanHolder; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.ReflectionUtils; + +/** + * 带返回值的线程 + * + * @author odboy + */ +@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 = KitSpringBeanHolder.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 + public Object call() throws Exception { + ReflectionUtils.makeAccessible(method); + if (StringUtils.isNotBlank(params)) { + return method.invoke(target, params); + } else { + return method.invoke(target, null); + } + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/DemoMessageSubscriber.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/DemoMessageSubscriber.java new file mode 100644 index 00000000..1c4f1f37 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/DemoMessageSubscriber.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2026 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 lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 消息订阅Demo + * + * @author odboy + */ +@Slf4j +@Component +public class DemoMessageSubscriber { + + public void onMessage(String message, String channel) { + log.info("收到来自redis消息队列的message={}, channel={}", message, channel); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/KitRedisHelper.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/KitRedisHelper.java new file mode 100644 index 00000000..d8d88c96 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/KitRedisHelper.java @@ -0,0 +1,881 @@ +/* + * Copyright 2021-2026 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 cn.odboy.framework.exception.BadRequestException; +import com.alibaba.fastjson2.JSON; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +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.ConvertingCursor; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.RedisConnectionUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ScanOptions; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.stereotype.Component; + +@Component +public class KitRedisHelper { + + private static final Logger log = LoggerFactory.getLogger(KitRedisHelper.class); + private final RedisTemplate redisTemplate; + + public KitRedisHelper(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 / + */ + @SuppressWarnings("all") + public List scan(String pattern) { + ScanOptions options = ScanOptions.scanOptions().match(pattern).build(); + RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); + RedisConnection rc = Objects.requireNonNull(factory).getConnection(); + List result; + try (Cursor cursor = rc.scan(options)) { + 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 / + */ + @SuppressWarnings("all") + 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 Boolean.TRUE.equals(redisTemplate.hasKey(key)); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + /** + * 删除缓存 + * + * @param keys 可以传一个值 或多个 + */ + public void del(String... keys) { + if (keys != null && keys.length > 0) { + if (keys.length == 1) { + boolean result = Boolean.TRUE.equals(redisTemplate.delete(keys[0])); + log.debug("删除缓存:{}, 结果:{}", keys[0], result); + } else { + Set keySet = new HashSet<>(); + for (String key : keys) { + if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) { + keySet.add(key); + } + } + Long count = redisTemplate.delete(keySet); + log.debug("成功删除缓存:{}", keySet); + log.debug("缓存删除数量:{}个", count); + } + } + } + + /** + * 批量模糊删除key + * + * @param pattern / + */ + @SuppressWarnings("all") + public void scanDel(String pattern) { + ScanOptions options = ScanOptions.scanOptions().match(pattern).count(100).build(); + try (Cursor cursor = redisTemplate.executeWithStickyConnection(connection -> { + try { + return (Cursor) new ConvertingCursor<>(connection.scan(options), redisTemplate.getKeySerializer()::deserialize); + } catch (Exception e) { + throw new RuntimeException("Redis scan operation failed", e); + } + })) { + List keysToDelete = new ArrayList<>(); + while (cursor.hasNext()) { + Object key = cursor.next(); + keysToDelete.add(key); + // 批量删除,避免一次性删除过多key + if (keysToDelete.size() >= 100) { + redisTemplate.delete(keysToDelete); + keysToDelete.clear(); + } + } + // 删除剩余的key + if (!keysToDelete.isEmpty()) { + redisTemplate.delete(keysToDelete); + } + } catch (Exception e) { + throw new RuntimeException("Error during scan and delete operation", e); + } + } + // ============================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 类型 + boolean isInstance = clazz.isInstance(value); + if (isInstance) { + return clazz.cast(value); + } + return JSON.parseObject(value.toString(), clazz); + } + + /** + * 普通缓存获取 + * + * @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) { + // 检查每个元素是否为指定类型 + 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, Duration.ofSeconds(time)); + } 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) { + if (TimeUnit.SECONDS.equals(timeUnit)) { + redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(time)); + } else if (TimeUnit.MINUTES.equals(timeUnit)) { + redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(time)); + } else if (TimeUnit.HOURS.equals(timeUnit)) { + redisTemplate.opsForValue().set(key, value, Duration.ofHours(time)); + } else { + throw new BadRequestException("不支持的类型"); + } + } 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 Boolean.TRUE.equals(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 { + Long add = redisTemplate.opsForSet().add(key, values); + return add == null ? 0L : add; + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0L; + } + } + + /** + * 将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 == null ? 0 : count; + } catch (Exception e) { + log.error(e.getMessage(), e); + return 0; + } + } + + /** + * 获取set缓存的长度 + * + * @param key 键 + * @return + */ + public long sGetSetSize(String key) { + try { + Long size = redisTemplate.opsForSet().size(key); + return size == null ? 0 : size; + } 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 == null ? 0 : 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(prefix + id)); + } + 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 { + Objects.requireNonNull(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 time 过期时间 + * @param timeUnit 时间单位 + * @return 如果设置成功返回true,否则返回false + */ + public boolean setIfAbsent(String key, Object value, long time, TimeUnit timeUnit) { + try { + String stringValue = ObjUtil.toString(value); + // 使用Redis的SETNX命令实现原子性操作 + Boolean success; + if (TimeUnit.SECONDS.equals(timeUnit)) { + success = redisTemplate.opsForValue().setIfAbsent(key, stringValue, Duration.ofSeconds(time)); + } else if (TimeUnit.MINUTES.equals(timeUnit)) { + success = redisTemplate.opsForValue().setIfAbsent(key, stringValue, Duration.ofMinutes(time)); + } else if (TimeUnit.HOURS.equals(timeUnit)) { + success = redisTemplate.opsForValue().setIfAbsent(key, stringValue, Duration.ofHours(time)); + } else { + throw new BadRequestException("不支持的类型"); + } + return success != null && success; + } catch (Exception e) { + log.error("Redis setIfAbsent操作失败, key: {}", key, e); + return false; + } + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/RedisConfiguration.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/RedisConfiguration.java new file mode 100644 index 00000000..cf13adac --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/RedisConfiguration.java @@ -0,0 +1,194 @@ +/* + * Copyright 2021-2026 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.odboy.framework.properties.AppProperties; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONFactory; +import com.alibaba.fastjson2.JSONWriter; +import jakarta.annotation.Nonnull; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.MurmurHash3; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.Cache; +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; + +@Slf4j +@Configuration +@EnableCaching +public class RedisConfiguration { + + @Autowired + private AppProperties properties; + + /** + * 设置 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 : properties.getSerialWhiteList()) { + JSONFactory.getDefaultObjectReaderProvider().addAutoTypeAccept(pack); + log.warn("设置fastJson的序列化白名单:{}", pack); + } + // key的序列化采用StringRedisSerializer + template.setKeySerializer(new StringRedisSerializer()); + template.setHashKeySerializer(new StringRedisSerializer()); + template.setConnectionFactory(redisConnectionFactory); + return template; + } + + /** + * 缓存管理器 + * + * @param redisConnectionFactory / + * @return 缓存管理器 + */ + @Bean + public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { + RedisCacheConfiguration config = redisCacheConfiguration(); + return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build(); + } + + /** + * 自定义缓存key生成策略 + */ + @Bean + public KeyGenerator keyGenerator() { + return (target, method, params) -> { + Map 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 + public CacheErrorHandler errorHandler() { + return new SimpleCacheErrorHandler() { + @Override + public void handleCacheGetError(@Nonnull RuntimeException exception, @Nonnull Cache cache, + @Nonnull Object key) { + // 处理缓存读取错误 + log.error("Cache Get Error: {}", exception.getMessage()); + } + + @Override + public void handleCachePutError(@Nonnull RuntimeException exception, @Nonnull Cache cache, + @Nonnull Object key, Object value) { + // 处理缓存写入错误 + log.error("Cache Put Error: {}", exception.getMessage()); + } + + @Override + public void handleCacheEvictError(@Nonnull RuntimeException exception, @Nonnull Cache cache, + @Nonnull Object key) { + // 处理缓存删除错误 + log.error("Cache Evict Error: {}", exception.getMessage()); + } + + @Override + public void handleCacheClearError(@Nonnull RuntimeException exception, @Nonnull 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); + } + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/RedisMessageConfig.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/RedisMessageConfig.java new file mode 100644 index 00000000..33fc84f4 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/redis/RedisMessageConfig.java @@ -0,0 +1,66 @@ +/* + * Copyright 2021-2026 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.odboy.framework.websocket.WsMessageSubscriber; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; + +/** + * Redis消息队列配置 + * + * @author odboy + */ +@Configuration +public class RedisMessageConfig { + + /** + * 消息监听容器 + * + * @param connectionFactory / + * @param listenerWsMessageAdapter / + * @return / + */ + @Bean + public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerWsMessageAdapter) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + // 指定监听的主题 + container.addMessageListener(listenerWsMessageAdapter, new ChannelTopic("DemoMessage")); + container.addMessageListener(listenerWsMessageAdapter, new ChannelTopic("WebSocketMessage")); + return container; + } + + /** + * 将订阅者与消息处理方法绑定 + * + * @param subscriber / + * @return / + */ + @Bean + public MessageListenerAdapter listenerDemoMessageAdapter(DemoMessageSubscriber subscriber) { + return new MessageListenerAdapter(subscriber, "onMessage"); + } + + @Bean + public MessageListenerAdapter listenerWsMessageAdapter(WsMessageSubscriber subscriber) { + return new MessageListenerAdapter(subscriber, "onMessage"); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/config/AsyncTheadPoolConfig.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/config/AsyncTheadPoolConfig.java new file mode 100644 index 00000000..281d1e7b --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/config/AsyncTheadPoolConfig.java @@ -0,0 +1,101 @@ +/* + * Copyright 2021-2026 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 java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +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; + +/** + * 创建自定义的线程池 + */ +@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-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringArraysToListDateConverter.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringArraysToListDateConverter.java new file mode 100644 index 00000000..89c711df --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringArraysToListDateConverter.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2026 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 java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +@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); + } + }).toList(); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringToDateConverter.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringToDateConverter.java new file mode 100644 index 00000000..93587068 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/converters/DateStringToDateConverter.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2026 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 java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +@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-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/core/ConfigurerAdapter.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/core/ConfigurerAdapter.java new file mode 100644 index 00000000..823ce981 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/core/ConfigurerAdapter.java @@ -0,0 +1,147 @@ +/* + * Copyright 2021-2026 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.spring6.http.converter.FastJsonHttpMessageConverter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +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.HttpMessageConverters.ServerBuilder; +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; + +/** + * 跨越、静态资源映射、Http消息转换 + * + * @author odboy + * @date 2025-03-21 + */ +@Configuration +@EnableWebMvc +public class ConfigurerAdapter implements WebMvcConfigurer { + + @Autowired + private DateStringArraysToListDateConverter dateStringArraysToListDateConverter; + @Autowired + private DateStringToDateConverter dateStringToDateConverter; + @Autowired + private KitFileLocalUploadHelper 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 = this.getFastJsonConfig(); +// fastJsonConverter.setFastJsonConfig(config); +// fastJsonConverter.setSupportedMediaTypes(supportMediaTypeList); +// fastJsonConverter.setDefaultCharset(StandardCharsets.UTF_8); +// // 将 FastJsonHttpMessageConverter 添加到列表末尾 +// converters.add(fastJsonConverter); +// } + + /** + * Http消息转换器 + */ + @Override + public void configureMessageConverters(ServerBuilder builder) { + // 添加默认的 StringHttpMessageConverter + builder.addCustomConverter(new StringHttpMessageConverter(StandardCharsets.UTF_8)); + // 配置 FastJsonHttpMessageConverter + FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter(); + List supportMediaTypeList = new ArrayList<>(); + supportMediaTypeList.add(MediaType.APPLICATION_JSON); + FastJsonConfig config = this.getFastJsonConfig(); + fastJsonConverter.setFastJsonConfig(config); + fastJsonConverter.setSupportedMediaTypes(supportMediaTypeList); + fastJsonConverter.setDefaultCharset(StandardCharsets.UTF_8); + // 将 FastJsonHttpMessageConverter 添加到列表末尾 + builder.addCustomConverter(fastJsonConverter); + WebMvcConfigurer.super.configureMessageConverters(builder); + } + + private FastJsonConfig getFastJsonConfig() { + 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); + return config; + } + + /** + * 自定义时间转换器 + */ + @Override + public void addFormatters(FormatterRegistry registry) { + registry.addConverter(dateStringArraysToListDateConverter); + registry.addConverter(dateStringToDateConverter); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/core/KitFileLocalUploadHelper.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/core/KitFileLocalUploadHelper.java new file mode 100644 index 00000000..072b0c70 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/server/core/KitFileLocalUploadHelper.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2026 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.hutool.core.util.StrUtil; +import cn.odboy.constant.SystemConst; +import cn.odboy.util.KitSystemUtil; +import org.springframework.stereotype.Component; + +/** + * 简化文件上传路径配置 + * + * @author odboy + */ +@Component +public class KitFileLocalUploadHelper { + + /** + * 单位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${username}/${appName}/file/"; + private static final String PATH_LINUX = "/home/${appName}/file/"; + + public String getPath() { + String os = System.getProperty("os.name"); + if (os == null) { + // 默认使用 Linux 路径作为兜底方案 + return PATH_LINUX.replace("${appName}", SystemConst.CURRENT_APP_NAME); + } + String lowerOs = os.toLowerCase(); + if (lowerOs.startsWith(SystemConst.WIN)) { + return PATH_WINDOWS.replace("${appName}", SystemConst.CURRENT_APP_NAME); + } else if (lowerOs.startsWith(SystemConst.MAC)) { + String currentUserName = KitSystemUtil.getCurrentUserName(); + if (StrUtil.isBlank(currentUserName)) { + return PATH_MAC.replace("${appName}", SystemConst.CURRENT_APP_NAME).replace("${username}", ""); + } + return PATH_MAC.replace("${appName}", SystemConst.CURRENT_APP_NAME).replace("${username}", "/" + currentUserName); + } + return PATH_LINUX.replace("${appName}", SystemConst.CURRENT_APP_NAME); + } + + public int getAvatarMaxSize() { + return AVATAR_MAX_SIZE; + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/KitWsBizCodeConst.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/KitWsBizCodeConst.java new file mode 100644 index 00000000..c656fef5 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/KitWsBizCodeConst.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021-2026 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; + +/** + * WebSocket业务代码常量 + * + * @author odboy + */ +public interface KitWsBizCodeConst { + + // 主动推送 + String AutomaticPush = "100"; + // 实时聊天 + String IM = "101"; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/KitWsMessagePublisher.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/KitWsMessagePublisher.java new file mode 100644 index 00000000..109b4eda --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/KitWsMessagePublisher.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021-2026 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; + +import cn.odboy.framework.websocket.context.KitWsMessage; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class KitWsMessagePublisher { + + @Autowired + private RedisTemplate redisTemplate; + + private KitWsMessage innerBuild(String bizCode, String fromUser, String toUser, String message) { + KitWsMessage wsMessage = new KitWsMessage(); + wsMessage.setBizCode(bizCode); + + KitWsMessage.Data data = new KitWsMessage.Data(); + data.setFormUser(fromUser); + data.setToUser(toUser); + data.setMessage(message); + wsMessage.setData(data); + + return wsMessage; + } + + public void automaticPush(String fromUser, String toUser, String message) { + KitWsMessage wsMessage = this.innerBuild(KitWsBizCodeConst.AutomaticPush, fromUser, toUser, message); + redisTemplate.convertAndSend("WebSocketMessage", wsMessage); + log.info("发送AutomaticPush消息"); + } + + public void imPush(String fromUser, String toUser, String message) { + KitWsMessage wsMessage = this.innerBuild(KitWsBizCodeConst.IM, fromUser, toUser, message); + redisTemplate.convertAndSend("WebSocketMessage", wsMessage); + log.info("发送IM消息"); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/WsMessageSubscriber.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/WsMessageSubscriber.java new file mode 100644 index 00000000..a476c586 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/WsMessageSubscriber.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2026 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; + +import cn.odboy.framework.websocket.context.KitWsMessage; +import cn.odboy.framework.websocket.context.KitWsServer; +import com.alibaba.fastjson2.JSON; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Ws消息订阅 + * + * @author odboy + */ +@Slf4j +@Component +public class WsMessageSubscriber { + + @Autowired + private KitWsServer kitWsServer; + +// /** +// * 无效写法 +// */ +// public void onMessage(KitWsMessage message) { +// log.info("收到来自redis消息队列的数据, {}", JSON.toJSONString(message)); +// KitWsMessage.Data data = message.getData(); +// kitWsServer.sendMessage(data.getMessage(), data.getToUsername()); +// } + + public void onMessage(String message, String channel) { + log.info("收到来自redis消息队列的message={}, channel={}", message, channel); + KitWsMessage wsMessage = JSON.parseObject(message, KitWsMessage.class); + kitWsServer.sendMessage(wsMessage.getData().getMessage(), wsMessage.getData().getToUser()); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/config/WebSocketEndpointConfig.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/config/WebSocketEndpointConfig.java new file mode 100644 index 00000000..e68a13a8 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/config/WebSocketEndpointConfig.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2026 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; + +/** + * 自动注册使用了 @ServerEndpoint 注解声明的 WebSocket 端点对象 + * + * @author odboy + * @date 2025-10-30 + */ +@Configuration +public class WebSocketEndpointConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/KitWsClientManager.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/KitWsClientManager.java new file mode 100644 index 00000000..79472d70 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/KitWsClientManager.java @@ -0,0 +1,64 @@ +/* + * Copyright 2021-2026 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 java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import lombok.extern.slf4j.Slf4j; + +/** + * WebSocket客户端管理 + * + * @author odboy + * @date 2025-07-24 + */ +@Slf4j +public class KitWsClientManager { + + /** + * concurrent包的线程安全Map, 用来存放每个客户端对应的MyWebSocket对象(已通过redis pub/sub解耦) + */ + private static final Map clientMap = new ConcurrentHashMap<>(); + + public static void addClient(String sid, KitWsServer wsServer) { + // 如果存在就先删除一个, 防止重复推送消息 + removeClient(sid); + clientMap.put(sid, wsServer); + } + + public static void removeClient(String sid) { + KitWsServer 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); + } + clientMap.remove(sid); + } + } + + public static Collection getAllClient() { + return clientMap.values(); + } + + public static KitWsServer getClientBySid(String sid) { + return clientMap.get(sid); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/KitWsMessage.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/KitWsMessage.java new file mode 100644 index 00000000..e8adecb5 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/KitWsMessage.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2026 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.KitObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class KitWsMessage extends KitObject { + + private String bizCode; + private Data data; + + @Getter + @Setter + public static class Data extends KitObject { + + private String formUser; + private String toUser; + private String message; + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/KitWsServer.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/KitWsServer.java new file mode 100644 index 00000000..c5c259c4 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/context/KitWsServer.java @@ -0,0 +1,159 @@ +/* + * Copyright 2021-2026 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.hutool.core.util.StrUtil; +import cn.odboy.framework.context.KitSpringBeanHolder; +import cn.odboy.framework.websocket.KitWsMessagePublisher; +import com.alibaba.fastjson2.JSON; +import jakarta.websocket.*; +import jakarta.websocket.server.PathParam; +import jakarta.websocket.server.ServerEndpoint; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Objects; + +@Slf4j +@Component +@ServerEndpoint("/websocket/{sid}") +@Getter +public class KitWsServer { + + /** + * 与某个客户端的连接会话, 需要通过它来给客户端发送数据 + */ + private Session session; + /** + * 接收sid + */ + private String sid = ""; + + /** + * 连接建立成功调用的方法 + */ + @OnOpen + public void onOpen(Session session, @PathParam("sid") String sid) { + this.session = session; + this.sid = sid; + KitWsClientManager.addClient(sid, this); + } + + /** + * 收到客户端消息后调用的方法 + * + * @param message 客户端发送过来的消息 + */ + @OnMessage + public void onMessage(String message, Session session) throws IOException { + if ("[{}]".equals(message)){ + try { + KitWsClientManager.removeClient(this.sid); + } catch (Exception e) { + log.error("WebSocket onClose error, sid={}", this.sid, e); + } + return; + } + KitWsMessage wsMessage = JSON.parseObject(message, KitWsMessage.class); +// String bizCode = wsMessage.getBizCode(); +// Object data = wsMessage.getData(); + log.info("收到来 sid={} 的信息: message={}", sid, message); +// session.getBasicRemote().sendText(message); + KitWsMessagePublisher messagePublisher = KitSpringBeanHolder.getBean(KitWsMessagePublisher.class); + messagePublisher.imPush(wsMessage.getData().getFormUser(), wsMessage.getData().getToUser(), wsMessage.getData().getMessage()); + } + + /** + * 连接关闭调用的方法 + */ + @OnClose + public void onClose() { + try { + KitWsClientManager.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 { + KitWsClientManager.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(String message, @PathParam("sid") String sid) { + log.info("推送消息到{}, 推送内容:{}", sid, message); + try { + if (StrUtil.isBlank(sid)) { + sendToAll(message); + } else { + KitWsServer wsClient = KitWsClientManager.getClientBySid(sid); + if (wsClient != null) { + wsClient.innerSendMessage(message); + } + } + } catch (IOException e) { + log.warn("推送消息到客户端失败", e); + } + } + + /** + * 群发消息 + */ + private void sendToAll(String message) { + for (KitWsServer item : KitWsClientManager.getAllClient()) { + try { + item.innerSendMessage(message); + } catch (IOException e) { + log.error("发送消息给 sid={} 失败", item.sid, e); + KitWsClientManager.removeClient(item.sid); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + KitWsServer that = (KitWsServer) o; + return Objects.equals(session, that.session) && Objects.equals(sid, that.sid); + } + + @Override + public int hashCode() { + return Objects.hash(session, sid); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/model/WsSidVo.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/model/WsSidVo.java new file mode 100644 index 00000000..57deeef4 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/model/WsSidVo.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2026 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.KitObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class WsSidVo extends KitObject { + + private String username; + private String bizCode; + private String param; +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/util/WsMessageUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/util/WsMessageUtil.java new file mode 100644 index 00000000..d54642e5 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/framework/websocket/util/WsMessageUtil.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitAnonTagUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitAnonTagUtil.java new file mode 100644 index 00000000..51c6a3a6 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitAnonTagUtil.java @@ -0,0 +1,112 @@ +/* + * Copyright 2021-2026 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.PathPatternsRequestCondition; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * 匿名标记工具 + */ +@UtilityClass +public class KitAnonTagUtil { + + /** + * 获取匿名标记的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()); + PathPatternsRequestCondition patternsCondition = infoEntry.getKey().getPathPatternsCondition(); + if (patternsCondition != null) { + switch (Objects.requireNonNull(request)) { + case GET: + get.addAll(patternsCondition.getPatternValues()); + break; + case POST: + post.addAll(patternsCondition.getPatternValues()); + break; + case PUT: + put.addAll(patternsCondition.getPatternValues()); + break; + case PATCH: + patch.addAll(patternsCondition.getPatternValues()); + break; + case DELETE: + delete.addAll(patternsCondition.getPatternValues()); + break; + default: + all.addAll(patternsCondition.getPatternValues()); + 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-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBase64SecretUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBase64SecretUtil.java new file mode 100644 index 00000000..f68fd677 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBase64SecretUtil.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2026 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 KitBase64SecretUtil { + + 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-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBeanUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBeanUtil.java new file mode 100644 index 00000000..47ed97b5 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBeanUtil.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2026 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.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import lombok.experimental.UtilityClass; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Bean拷贝相关 + * + * @author odboy + * @date 2026-01-06 + */ +@UtilityClass +public class KitBeanUtil { + + public static T copyToClass(Object source, Class tClass) { + if (source == null) { + return null; + } + return BeanUtil.copyProperties(source, tClass); + } + + public static void copyToTarget(Object source, Object target) { + if (source == null || target == null) { + return; + } + BeanUtil.copyProperties(source, target, CopyOptions.create().setIgnoreNullValue(true)); + } + + public static List copyToList(Collection collection, Class targetType) { + if (collection == null) { + return new ArrayList<>(0); + } + if (collection.isEmpty()) { + return new ArrayList<>(0); + } + return BeanUtil.copyToList(collection, targetType); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBigDecimalUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBigDecimalUtil.java new file mode 100644 index 00000000..76d84de0 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBigDecimalUtil.java @@ -0,0 +1,183 @@ +/* + * Copyright 2021-2026 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; +import java.text.NumberFormat; + +/** + * 计算类 + */ +@UtilityClass +public final class KitBigDecimalUtil { + + /** + * 将对象转换为 BigDecimal + * + * @param obj 输入对象 + * @return 转换后的 BigDecimal + */ + private static BigDecimal toBigDecimal(Object obj) { + return switch (obj) { + case BigDecimal result -> result; + case Long result -> BigDecimal.valueOf(result); + case Integer result -> BigDecimal.valueOf(result.longValue()); + case Short result -> BigDecimal.valueOf(result.longValue()); + case Byte result -> BigDecimal.valueOf(result.longValue()); + case Double result -> BigDecimal.valueOf(result); + case Float result -> BigDecimal.valueOf(result.doubleValue()); + case String result -> new BigDecimal(result); + case Number result -> BigDecimal.valueOf(result.doubleValue()); + case null, default -> { + assert obj != null; + throw new IllegalArgumentException("Unsupported type: " + obj.getClass().getName()); + } + }; + } + + /** + * 加法 + * + * @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); + } + + /** + * 减法 + * + * @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); + } + + /** + * 乘法 + * + * @param a 乘数 + * @param b 乘数 + * @return 两个乘数的积, 保留6位小数 + */ + public static BigDecimal multiply(Object a, Object b) { + BigDecimal bdA = toBigDecimal(a); + BigDecimal bdB = toBigDecimal(b); + return bdA.multiply(bdB); + } + + /** + * 除法 + * + * @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, 6, RoundingMode.DOWN); + } + + /** + * 分转元 + * + * @param obj 分的金额 + * @return 转换后的元 + */ + public static BigDecimal centsToYuan(Object obj) { + BigDecimal cents = toBigDecimal(obj); + return cents.divide(BigDecimal.valueOf(100), 6, RoundingMode.DOWN); + } + + /** + * 元转分 + * + * @param obj 元的金额 + * @return 转换后的分 + */ + public static long yuanToCents(Object obj) { + BigDecimal yuan = toBigDecimal(obj); + return yuan.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_EVEN).longValue(); + } + + /** + * 取整,采用银行家算法 + * + * @param obj 金额 + * @return / + */ + public static BigDecimal toRound(Object obj) { + return toBigDecimal(obj).setScale(0, RoundingMode.HALF_EVEN); + } + + /** + * 取整,采用银行家算法 + * + * @param obj 金额 + * @return / + */ + public static long toRoundLong(Object obj) { + return toBigDecimal(obj).setScale(0, RoundingMode.HALF_EVEN).longValue(); + } + + /** + * 格式化为 xxx,xxx,xxx 格式 + * + * @param obj 金额 + * @return / + */ + public static String formatGroupingAmount(Object obj) { + if (obj == null) { + return "0"; + } + BigDecimal bigDecimal = toBigDecimal(obj); + NumberFormat nf = NumberFormat.getInstance(); + // 启用千位分隔符 + nf.setGroupingUsed(true); + return nf.format(bigDecimal.setScale(0, RoundingMode.HALF_UP)); + } + + 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)); + BigDecimal toRo = new BigDecimal("123.1548456"); + System.out.println("取整: " + toRound(toRo)); + BigDecimal toRoLong = new BigDecimal("123.1548456"); + System.out.println("取整转long: " + toRoundLong(toRoLong)); + BigDecimal formatGa = new BigDecimal("1231548456123123"); + System.out.println("格式化千分位: " + formatGroupingAmount(formatGa)); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBrowserUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBrowserUtil.java new file mode 100644 index 00000000..b620713c --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitBrowserUtil.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021-2026 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 jakarta.servlet.http.HttpServletRequest; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * 浏览器 相关 + */ +@Slf4j +@UtilityClass +public final class KitBrowserUtil { + + 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-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitClassUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitClassUtil.java new file mode 100644 index 00000000..fc039e60 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitClassUtil.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2026 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 KitClassUtil { + + 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-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitCloseUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitCloseUtil.java new file mode 100644 index 00000000..3816799b --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitCloseUtil.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2026 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 KitCloseUtil { + + 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-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitCollUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitCollUtil.java new file mode 100644 index 00000000..4a536641 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitCollUtil.java @@ -0,0 +1,223 @@ +/* + * Copyright 2021-2026 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.KitSelectOptionVo; +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.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +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 KitCollUtil 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 (KitCollUtil.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 (KitCollUtil.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) { + KitValidUtil.isEmpty(data, "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<>(); + KitSelectOptionVo selectOption1 = KitSelectOptionVo.builder().label("苹果").value("apple").build(); + KitSelectOptionVo selectOption2 = KitSelectOptionVo.builder().label("苹果").value("apple").build(); + KitSelectOptionVo selectOption3 = KitSelectOptionVo.builder().label("香蕉").value("banana").build(); + KitSelectOptionVo selectOption4 = KitSelectOptionVo.builder().label("猫").value("cat").build(); + testData.add(selectOption1); + testData.add(selectOption2); + testData.add(selectOption3); + testData.add(selectOption4); + List selectOptions1 = KitCollUtil.streamAscSort(testData, KitSelectOptionVo::getValue); + System.out.println(JSON.toJSONString(selectOptions1)); + List selectOptions2 = KitCollUtil.streamDescSort(testData, KitSelectOptionVo::getValue); + System.out.println(JSON.toJSONString(selectOptions2)); + List selectOptions3 = KitCollUtil.streamDistinct(testData, KitSelectOptionVo::getValue); + System.out.println(JSON.toJSONString(selectOptions3)); + Map selectMap1 = + KitCollUtil.streamToMap(testData, KitSelectOptionVo::getValue, KitSelectOptionVo::getLabel); + System.out.println(JSON.toJSONString(selectMap1)); + // 这只是一个示范 + BigDecimal total1 = KitCollUtil.streamBigDecimalAdd(testData, "value"); + System.out.println(total1); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitDateUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitDateUtil.java new file mode 100644 index 00000000..4bfe19fa --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitDateUtil.java @@ -0,0 +1,166 @@ +/* + * Copyright 2021-2026 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.DateTime; +import cn.hutool.core.date.DateUtil; +import java.util.Calendar; +import java.util.Date; +import lombok.experimental.UtilityClass; + +/** + * 时间相关工具 + * + * @author odboy + * @date 2025-10-01 + */ +@UtilityClass +public class KitDateUtil { + + /** + * 获取当前时间毫秒数 yyyyMMddHHmmssSSS + * + * @return / + */ + public static String getNowDateTimeMsStr() { + return DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN); + } + + /** + * 获取当前时间,但不包括毫秒数 yyyyMMddHHmmss + * + * @return / + */ + public static String getNowDateTimeStr() { + return DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN); + } + + /** + * 获取当前时间,但不包括毫秒数 yyyyMMdd + * + * @return / + */ + public static String getNowDateStr() { + return DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN); + } + + /** + * 格式化到毫秒 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 格式化后的时间,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(); + } + + /** + * 获得当天是周几 + */ + public static String getWeekDay() { + 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]; + } + + /** + * 格式化毫秒时间戳字符串为'yyyy-MM-dd'格式 + * + * @param timestamp 例如:1767196800000 + * @return 例如:2026-01-01 + */ + public static String formatMsTimestamp(String timestamp) { + String formatDate; + if (timestamp.matches("\\d{13}")) { + long ts = Long.parseLong(timestamp); + formatDate = DateUtil.format(new Date(ts), DatePattern.NORM_DATE_PATTERN); + } else { + DateTime dateTime = DateTime.of(timestamp, DatePattern.NORM_DATETIME_PATTERN); + formatDate = DateUtil.format(dateTime, DatePattern.NORM_DATE_PATTERN); + } + return formatDate; + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitDesEncryptUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitDesEncryptUtil.java new file mode 100644 index 00000000..094d4291 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitDesEncryptUtil.java @@ -0,0 +1,94 @@ +/* + * Copyright 2021-2026 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 KitDesEncryptUtil { + + 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-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitDistributedIdUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitDistributedIdUtil.java new file mode 100644 index 00000000..a2f00ba7 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitDistributedIdUtil.java @@ -0,0 +1,196 @@ +/* + * Copyright 2021-2026 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 + */ +@Deprecated +public final class KitDistributedIdUtil { + + /** + * 起始毫秒时间戳, 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); + + /** + * 构造函数, 初始化数据中心 ID 和机器 ID + * + * @param dataCenterId 数据中心 ID + * @param workerId 机器 ID + * @param dataCenterIdBits 数据中心 ID 所占位数 + * @param workerIdBits 机器 ID 所占位数 + * @param sequenceBits 序列号所占位数 + */ + public KitDistributedIdUtil(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 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 + " - " + KitDistributedIdUtil.fastNextId()); + } + }).start(); + } + } + + /** + * 生成下一个分布式 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; + } + + /** + * 单例实例持有者类 + */ + private static class InstanceHolder { + + private static final KitDistributedIdUtil INSTANCE = new KitDistributedIdUtil(20, 12, 5, 5, 12); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitFileUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitFileUtil.java new file mode 100644 index 00000000..9e19208d --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitFileUtil.java @@ -0,0 +1,400 @@ +/* + * Copyright 2021-2026 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.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +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 jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +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.SYMBOL_ADD; +import static cn.odboy.constant.SystemConst.SYMBOL_AT; +import static cn.odboy.constant.SystemConst.SYMBOL_EQUAL; +import static cn.odboy.constant.SystemConst.SYMBOL_SUBTRACT; + +/** + * File工具类, 扩展 hutool 工具包 + */ +@Slf4j +@UtilityClass +public final class KitFileUtil extends 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; + + /** + * 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 = new DecimalFormat("0.00").format(size / (float) GB) + " GB"; + } else if (size / MB >= 1) { + //如果当前Byte的值大于等于1MB + resultSize = new DecimalFormat("0.00").format(size / (float) MB) + " MB"; + } else if (size / KB >= 1) { + //如果当前Byte的值大于等于1KB + resultSize = new DecimalFormat("0.00").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 { + KitCloseUtil.close(os); + KitCloseUtil.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 + */ + @Deprecated + 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 { + KitCloseUtil.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)); + } + + /** + * 获取文件名前缀 + * + * @param originalFilename 原始文件名 + * @param firstName 首选文件名,如果不为空,则返回 + * @return / + */ + public static String getPrefix(String originalFilename, String firstName) { + return StrUtil.isBlank(firstName) ? getPrefix(originalFilename) : firstName; + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitIPUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitIPUtil.java new file mode 100644 index 00000000..2e5eec1d --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitIPUtil.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021-2026 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.KitSpringBeanHolder; +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 KitIPUtil { + + /** + * 注入bean + */ + private final static Ip2regionSearcher IP_SEARCHER = KitSpringBeanHolder.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-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitJsonUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitJsonUtil.java new file mode 100644 index 00000000..26f3afd9 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitJsonUtil.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2026 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 KitJsonUtil { + + 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(KitJsonUtil.toMap(mapStr, String.class, String.class)); + String listStr = "[\"value1\", \"value2\"]"; + System.err.println(KitJsonUtil.toList(listStr, String.class)); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitListUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitListUtil.java new file mode 100644 index 00000000..c83a16e5 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitListUtil.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2026 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.KitBaseUserTimeTb; +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 KitListUtil { + + /** + * 根据对象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(List::getFirst).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++) { + KitBaseUserTimeTb entity = new KitBaseUserTimeTb(); + 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 = KitListUtil.distinctByArgs(entities, KitBaseUserTimeTb::getCreateBy); + System.err.println(entities1); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitLogFmtUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitLogFmtUtil.java new file mode 100644 index 00000000..b924089c --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitLogFmtUtil.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2026 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 KitLogFmtUtil { + + /** + * 替换字符串中的占位符 {} 为传入的参数 + * + * @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-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitPageUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitPageUtil.java new file mode 100644 index 00000000..5b5da4da --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitPageUtil.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2026 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.KitPageResult; +import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.experimental.UtilityClass; +import java.util.Collections; +import java.util.List; + +/** + * 分页工具 + * + * @author odboy + */ +@UtilityClass +public final class KitPageUtil 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 KitPageResult toPage(IPage page) { + return new KitPageResult<>(page.getRecords(), page.getTotal()); + } + + /** + * 自定义分页 + */ + public static KitPageResult toPage(List list) { + return new KitPageResult<>(list, list.size()); + } + + /** + * 返回空数据 + */ + public static KitPageResult emptyData() { + return new KitPageResult<>(null, 0); + } + + /** + * 自定义分页 + */ + public static KitPageResult toPage(List list, long totalElements) { + return new KitPageResult<>(list, totalElements); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitResourceTemplateUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitResourceTemplateUtil.java new file mode 100644 index 00000000..5bec118b --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitResourceTemplateUtil.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2026 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 KitResourceTemplateUtil { + + 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-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitReturnValueHandleUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitReturnValueHandleUtil.java new file mode 100644 index 00000000..72d70eaa --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitReturnValueHandleUtil.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021-2026 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 KitReturnValueHandleUtil { + + 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-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitRsaEncryptUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitRsaEncryptUtil.java new file mode 100644 index 00000000..29cbe2b0 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitRsaEncryptUtil.java @@ -0,0 +1,217 @@ +/* + * Copyright 2021-2026 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.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +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 KitRsaEncryptUtil { + + 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(), KitRsaEncryptUtil.SRC); + String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1); + System.out.println("加密前:" + KitRsaEncryptUtil.SRC); + System.out.println("加密后:" + text1); + System.out.println("解密后:" + text2); + if (KitRsaEncryptUtil.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(), KitRsaEncryptUtil.SRC); + String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1); + System.out.println("加密前:" + KitRsaEncryptUtil.SRC); + System.out.println("加密后:" + text1); + System.out.println("解密后:" + text2); + if (KitRsaEncryptUtil.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(4096); + 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-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java new file mode 100644 index 00000000..3f5e01dd --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java @@ -0,0 +1,98 @@ +/* + * Copyright 2021-2026 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; + +/** + * 字符串工具类, 继承org.apache.commons.lang3.StringUtils类 + */ +@Slf4j +@UtilityClass +public final class KitStringUtil { + + 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(); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitSystemUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitSystemUtil.java new file mode 100644 index 00000000..d99ed29c --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitSystemUtil.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2026 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 KitSystemUtil { + + public static int getCpuCount() { + SystemInfo systemInfo = new SystemInfo(); + CentralProcessor processor = systemInfo.getHardware().getProcessor(); + return processor.getPhysicalProcessorCount(); + } + + /** + * 获取当前系统的用户名 + */ + public static String getCurrentUserName() { + return System.getProperty("user.name"); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitThreadUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitThreadUtil.java new file mode 100644 index 00000000..4f9488b3 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitThreadUtil.java @@ -0,0 +1,17 @@ +package cn.odboy.util; + +import cn.hutool.core.thread.ThreadUtil; + +public class KitThreadUtil { + + /** + * 可中断的睡眠 + */ + public static void interruptSleep(int i) throws InterruptedException { + Thread.sleep(i); + } + + public static void safeSleep(int i) { + ThreadUtil.safeSleep(i); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitValidUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitValidUtil.java new file mode 100644 index 00000000..14a1a206 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitValidUtil.java @@ -0,0 +1,184 @@ +/* + * Copyright 2021-2026 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.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.framework.exception.BadRequestException; +import lombok.experimental.UtilityClass; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.groups.Default; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Bean验证工具 + * + * @author odboy + * @date 2025-01-15 + */ +@UtilityClass +public final class KitValidUtil { + + private static final Validator VALIDATOR; + + static { + try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) { + VALIDATOR = factory.getValidator(); + } + } + + /** + * 验证实体是否通过校验
实体内通过 javax.validation.constraints 中的表达式限制参数
+ * + * @param object / + * @param / + */ + 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(","))); + } + } + + /** + * 参数为null,抛异常 + * + * @param value 参数值 + */ + public static void isNull(Object value) { + if (value == null) { + throw new BadRequestException("参数必填"); + } + } + + /** + * 参数为空,抛异常 + * + * @param value 参数值 + * @param name 参数名称 + * @param code 参数编码 + */ + public static void isNull(Object value, @NotNull String name, @NotNull String code) { + if (value == null) { + throw new BadRequestException(String.format("参数 %s(%s) 必填", name, code)); + } + } + + /** + * 参数为空,抛异常 + * + * @param value 参数值 + * @param message 异常信息 + */ + public static void isNull(Object value, @NotNull String message) { + if (value == null) { + throw new BadRequestException(message); + } + } + + /** + * 参数为空,抛异常 + * + * @param value 参数值 + * @param code 参数编码 + */ + public static void isNullF(Object value, @NotNull String code) { + if (value == null) { + throw new BadRequestException(String.format("参数 %s 必填", code)); + } + } + + /** + * 参数为空字符串,抛异常 + * + * @param value 参数值 + * @param name 参数名称 + * @param code 参数编码 + */ + public static void isBlank(CharSequence value, @NotNull String name, @NotNull String code) { + if (StrUtil.isBlank(value)) { + throw new BadRequestException(String.format("参数 %s(%s) 必填", name, code)); + } + } + + /** + * 集合为空,抛异常 + * + * @param data 集合值 + * @param code 参数编码 + */ + public static void isEmpty(List data, @NotNull String code) { + if (CollUtil.isEmpty(data)) { + throw new BadRequestException(String.format("参数 %s 必填", code)); + } + } + + /** + * 集合为空,抛异常 + * + * @param data 集合值 + * @param name 参数名称 + * @param code 参数编码 + */ + public static void isEmpty(List data, @NotNull String name, @NotNull String code) { + if (CollUtil.isEmpty(data)) { + throw new BadRequestException(String.format("参数 %s(%s) 必填", name, code)); + } + } + + /** + * 表达式为true,抛异常 + * + * @param condition 表达式 + * @param message 异常信息 + */ + public static void isTrue(boolean condition, String message) { + if (condition) { + throw new BadRequestException(message); + } + } + + /** + * 表达式为false,抛异常 + * + * @param condition 表达式 + * @param message 异常信息 + */ + public static void isFalse(boolean condition, String message) { + if (!condition) { + throw new BadRequestException(message); + } + } + + /** + * 参数为空字符串,抛异常 + * + * @param value 参数值 + * @param message 异常信息 + */ + public static void isBlank(String value, String message) { + if (StrUtil.isBlank(value)) { + throw new BadRequestException(message); + } + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitExcelExporter.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitExcelExporter.java new file mode 100644 index 00000000..b36f3c3f --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitExcelExporter.java @@ -0,0 +1,101 @@ +package cn.odboy.util.xlsx; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.idev.excel.EasyExcel; +import jakarta.servlet.http.HttpServletResponse; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.List; + +/** + * EasyExcel 导出增强工具类 (支持普通/分页模式) + * + * @author https://mp.weixin.qq.com/s/gnrKCUzHg24sBSDp4IyAvg + */ +public class KitExcelExporter { + + private static int calculateOptimalPageSize(Class clazz) { + // 1. 估算单条数据大小 (字节) - 根据业务模型调整逻辑 + // 保守估计500字节/行 + long approxBytesPerRow = 500; + // 2. 获取当前JVM可用内存 (转成字节) + long freeMemoryBytes = Runtime.getRuntime().freeMemory(); + // 3. 【安全策略】仅使用一部分可用内存 (例如 40%) + long safeMemoryToUse = (long) (freeMemoryBytes * 0.4); + // 4. 计算建议分页条数 + int suggestedPageSize = (int) (safeMemoryToUse / approxBytesPerRow); + // 5. 设置合理范围 (防止太大或太小) + // 限制在1000~10000条/页 + return Math.max(1000, Math.min(suggestedPageSize, 10000)); + } + + /** + * 分页写入 (大数据量首选) + * + * @param response / + * @param fileName 下载文件名 + * @param dataModel 数据类 (User.class) + * @param totalCount 总条数 + * @param pageSupplier 分页查询逻辑 + */ + public static void exportByPage(HttpServletResponse response, String fileName, Class dataModel, long totalCount, PageQuerySupplier pageSupplier) { + int pageSize = calculateOptimalPageSize(dataModel); + String sheetName = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN); + setupResponse(response, fileName, sheetName); + try (OutputStream out = response.getOutputStream()) { + KitExcelPageWriteHelper.writeByPage(out, dataModel, sheetName, pageSize, totalCount, pageSupplier); + } catch (Exception e) { + throw new RuntimeException("导出失败: " + e.getMessage(), e); + } + } + + /** + * 普通导出 (小数据量) + * + * @param response / + * @param fileName 下载文件名 + * @param dataModel 数据类 (User.class) + * @param dataList 全量数据List + */ + public static void exportSimple(HttpServletResponse response, String fileName, Class dataModel, List dataList) { + String sheetName = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN); + setupResponse(response, fileName, sheetName); + try (OutputStream out = response.getOutputStream()) { + EasyExcel.write(out, dataModel) + .sheet(sheetName) + // 全量写入 + .doWrite(dataList); + } catch (Exception e) { + throw new RuntimeException("导出失败: " + e.getMessage(), e); + } + } + + /** + * 设置响应头 + */ + private static void setupResponse(HttpServletResponse response, String title, String sheetName) { + try { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + String fileName = URLEncoder.encode(sheetName + "_" + title, StandardCharsets.UTF_8); + response.setHeader("Content-disposition", + "attachment;filename*=utf-8''" + fileName + ".xlsx"); + } catch (Exception e) { + throw new RuntimeException("设置响应头失败", e); + } + } + + /** + * 分页查询供应商 + * + * @param + */ + @FunctionalInterface + public interface PageQuerySupplier { + + List getPage(long pageNum, long pageSize); // 函数式接口 + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitExcelPageWriteHelper.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitExcelPageWriteHelper.java new file mode 100644 index 00000000..c6c399af --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitExcelPageWriteHelper.java @@ -0,0 +1,36 @@ +package cn.odboy.util.xlsx; + +import cn.idev.excel.EasyExcel; +import cn.idev.excel.ExcelWriter; +import cn.idev.excel.write.metadata.WriteSheet; +import java.io.OutputStream; +import java.util.List; + +/** + * 分页写入Excel工具 - 专治各种不服(OOM) + */ +public class KitExcelPageWriteHelper { + + /** + * 执行分页写入 + * + * @param outputStream 输出流 (响应OutputStream) + * @param head 数据模型Class (如 User.class) + * @param sheetName 表格名称 + * @param pageSize 【重要】每批次处理条数 (建议 1000~5000) + * @param totalCount 总数据量 (用于计算总页数) + * @param supplier 分页数据提供器 (你的业务查询逻辑) + */ + public static void writeByPage(OutputStream outputStream, Class head, String sheetName, long pageSize, long totalCount, KitExcelExporter.PageQuerySupplier supplier) { + try (ExcelWriter excelWriter = EasyExcel.write(outputStream, head).build()) { + // 默认Sheet + WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build(); + int totalPage = totalCount > 0 ? (int) Math.ceil((double) totalCount / pageSize) : 1; + for (int pageNum = 1; pageNum <= totalPage; pageNum++) { + List pageData = supplier.getPage(pageNum, pageSize); + excelWriter.write(pageData, writeSheet); + pageData.clear(); + } + } + } +} \ No newline at end of file diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitXlsxExportDataTransferRowHandler.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitXlsxExportDataTransferRowHandler.java new file mode 100644 index 00000000..41c366d4 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitXlsxExportDataTransferRowHandler.java @@ -0,0 +1,8 @@ +package cn.odboy.util.xlsx; + +import java.util.List; + +public interface KitXlsxExportDataTransferRowHandler { + + List doHandler(O o); +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitXlsxExportUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitXlsxExportUtil.java new file mode 100644 index 00000000..520e511d --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/xlsx/KitXlsxExportUtil.java @@ -0,0 +1,62 @@ +package cn.odboy.util.xlsx; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import cn.idev.excel.FastExcel; +import cn.odboy.framework.exception.BadRequestException; +import lombok.experimental.UtilityClass; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * xlsx文件导出工具 + * + * @author odboy + * @date 2025-12-29 + */ +@UtilityClass +public class KitXlsxExportUtil { + + /** + * @param response HttpServletResponse + * @param title 标题 + * @param dataObjectList 数据源,一般为从数据库直接查询的数据 + * @param targetClazz 目标对象Class + * @param rowHandler 数据对象 -> 目标对象 映射逻辑 + * @param 数据源对象类型 + * @param 目标对象类型 + * @throws IOException + */ + public static void exportFile(HttpServletResponse response, String title, List dataObjectList, Class targetClazz, KitXlsxExportDataTransferRowHandler rowHandler) throws IOException { + if (response == null) { + throw new BadRequestException("参数 HttpServletResponse(response) 必填"); + } + if (CollUtil.isEmpty(dataObjectList)) { + throw new BadRequestException("参数 数据源(dataObjectList) 必填"); + } + if (StrUtil.isBlank(title)) { + throw new BadRequestException("参数 标题(title) 必填"); + } + String sheetName = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN); + List resultList = new ArrayList<>(); + for (O o : dataObjectList) { + List rows = rowHandler.doHandler(o); + resultList.addAll(rows); + } + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + String fileName = URLEncoder.encode(sheetName + "_" + title, StandardCharsets.UTF_8); + response.setHeader("Content-disposition", + "attachment;filename*=utf-8''" + fileName + ".xlsx"); + FastExcel.write(response.getOutputStream(), targetClazz) + .sheet(sheetName) + .doWrite(resultList); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/pom.xml b/cutejava-springboot4/cutejava-module-system/pom.xml new file mode 100644 index 00000000..f154e1d8 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/pom.xml @@ -0,0 +1,25 @@ + + + + cutejava-springboot4 + cn.odboy + 1.5.2 + + 4.0.0 + cutejava-module-system + 核心模块 + + + cn.odboy + cutejava-framework + 1.5.2 + + + + io.minio + minio + 8.5.17 + + + diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteDeptTreeApi.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteDeptTreeApi.java new file mode 100644 index 00000000..3263e041 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteDeptTreeApi.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2026 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.application; + +import cn.odboy.system.application.model.CuteDeptTreeVo; +import cn.odboy.system.application.service.CuteDeptTreeService; +import cn.odboy.system.dal.model.request.SystemQueryDeptArgs; +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.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 java.util.List; + +@RestController +@Api(tags = "系统组件:CuteDeptTree") +@RequestMapping("/api/component/CuteDeptTree") +public class CuteDeptTreeApi { + + @Autowired + private CuteDeptTreeService cuteDeptTreeService; + + @ApiOperation("查询部门") + @PostMapping(value = "/listMetadata") + public ResponseEntity> listMetadata(@Validated @RequestBody SystemQueryDeptArgs args) throws Exception { + List depts = cuteDeptTreeService.listMetadata(args); + return ResponseEntity.ok(depts); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteFileUploadApi.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteFileUploadApi.java new file mode 100644 index 00000000..fe44b051 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteFileUploadApi.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021-2026 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.application; + +import cn.odboy.system.application.service.CuteFileUploadService; +import cn.odboy.system.framework.operalog.OperationLog; +import cn.odboy.system.service.SystemOssStorageService; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@Api(tags = "系统组件:CuteFileUpload") +@RequestMapping("/api/component/CuteFileUpload") +public class CuteFileUploadApi { + + @Autowired + private CuteFileUploadService cuteFileUploadService; + @Autowired + private SystemOssStorageService systemOssStorageService; + + @OperationLog + @ApiOperation("上传文件到服务器本地") + @PostMapping(value = "/uploadLocal") + @PreAuthorize("@el.check()") + public ResponseEntity uploadLocal(@RequestParam("file") MultipartFile file) { + String fileUrl = cuteFileUploadService.uploadLocal(file); + return ResponseEntity.ok(fileUrl); + } + + @OperationLog + @ApiOperation("上传文件到OSS") + @PostMapping(value = "/uploadOSS") + @PreAuthorize("@el.check()") + public ResponseEntity uploadOSS(@RequestParam("file") MultipartFile file) { + String fileUrl = systemOssStorageService.uploadFile(file); + return ResponseEntity.ok(fileUrl); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteProductLineSelectApi.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteProductLineSelectApi.java new file mode 100644 index 00000000..7a649e9c --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteProductLineSelectApi.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2026 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.application; + +import cn.odboy.system.application.service.CuteProductLineSelectService; +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 = "系统组件:CuteProductLineSelect") +@RequestMapping("/api/component/CuteProductLineSelect") +public class CuteProductLineSelectApi { + + @Autowired + private CuteProductLineSelectService cuteProductLineSelectService; + + @ApiOperation("查询部门下拉选择数据源") + @PostMapping(value = "/listMetadata") + @PreAuthorize("@el.check()") + public ResponseEntity listMetadata() { + return ResponseEntity.ok(cuteProductLineSelectService.listMetadata()); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteProductLineSelectProApi.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteProductLineSelectProApi.java new file mode 100644 index 00000000..76960766 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteProductLineSelectProApi.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2026 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.application; + +import cn.odboy.system.application.service.CuteProductLineSelectProService; +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 = "系统组件:CuteProductLineSelectPro") +@RequestMapping("/api/component/CuteProductLineSelectPro") +public class CuteProductLineSelectProApi { + + @Autowired + private CuteProductLineSelectProService cuteProductLineSelectProService; + + @ApiOperation("查询部门下拉选择Pro数据源") + @PostMapping(value = "/listMetadata") + @PreAuthorize("@el.check()") + public ResponseEntity listMetadata() { + return ResponseEntity.ok(cuteProductLineSelectProService.listMetadata()); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteUserSelectApi.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteUserSelectApi.java new file mode 100644 index 00000000..18101bf5 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/CuteUserSelectApi.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2026 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.application; + +import cn.odboy.system.application.model.CuteUserSelectVo; +import cn.odboy.system.application.service.CuteUserSelectService; +import cn.odboy.system.dal.model.request.SystemQueryUserArgs; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import java.util.List; + +@RestController +@Api(tags = "系统组件:CuteUserSelect") +@RequestMapping("/api/component/CuteUserSelect") +public class CuteUserSelectApi { + + @Autowired + private CuteUserSelectService cuteUserSelectService; + + @ApiOperation("查询用户基础数据") + @PostMapping(value = "/listMetadata") + @PreAuthorize("@el.check()") + public ResponseEntity> listMetadata(@Validated @RequestBody SystemQueryUserArgs args) { + List collect = cuteUserSelectService.listMetadata(args); + return ResponseEntity.ok(collect); + } + + @ApiOperation("根据用户名称集合查询用户基础数据") + @PostMapping(value = "/listMetadataByUsernames") + @PreAuthorize("@el.check()") + public ResponseEntity> listMetadataByUsernames(@RequestBody List usernameList) { + List collect = cuteUserSelectService.listMetadataByUsernames(usernameList); + return ResponseEntity.ok(collect); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteDeptTreeVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteDeptTreeVo.java new file mode 100644 index 00000000..c3585939 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteDeptTreeVo.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2026 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.application.model; + +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CuteDeptTreeVo extends KitObject { + + /** + * 部门ID + */ + private String id; + /** + * 部门名称 + */ + private String name; + /** + * 上级部门ID + */ + private String pid; + /** + * 子节点数目 + */ + private Integer subCount = 0; + + /** + * 是否有子节点 + */ + public Boolean getHasChildren() { + return subCount > 0; + } + + /** + * 是否为叶子 + */ + public Boolean getLeaf() { + return subCount <= 0; + } + + /** + * 标签名称 + */ + public String getLabel() { + return name; + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteProductLineSelectProVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteProductLineSelectProVo.java new file mode 100644 index 00000000..8577fb1e --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteProductLineSelectProVo.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2026 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.application.model; + +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; +import java.util.List; + +@Getter +@Setter +public class CuteProductLineSelectProVo extends KitObject { + /** + * 部门ID(select的value) + */ + private String value; + /** + * 产品线,如"研发部/华南分部/devops组" + */ + private String label; + private List children; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteProductLineSelectVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteProductLineSelectVo.java new file mode 100644 index 00000000..eae8f15a --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteProductLineSelectVo.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2026 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.application.model; + +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CuteProductLineSelectVo extends KitObject { + + /** + * 部门ID(select的value) + */ + private String value; + /** + * 产品线,如"研发部/华南分部/devops组" + */ + private String label; + /** + * ID路径,如"1-2-3" + */ + private String idPath; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteUserSelectVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteUserSelectVo.java new file mode 100644 index 00000000..5efe95be --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/model/CuteUserSelectVo.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2026 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.application.model; + +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CuteUserSelectVo extends KitObject { + + /** + * username + */ + private String value; + /** + * nickname + */ + private String label; + /** + * 部门ID + */ + private String deptId; + /** + * 邮箱 + */ + private String email; + /** + * 手机号 + */ + private String phone; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteBaseService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteBaseService.java new file mode 100644 index 00000000..d87b93f3 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteBaseService.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2026 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.application.service; + +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.mysql.SystemDeptMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; + +public class CuteBaseService { + + @Autowired + private SystemDeptMapper systemDeptMapper; + + /** + * 查询启用的所有部门集合 + * + * @return / + */ + public List listEnabledDept() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemDeptTb::getEnabled, 1); + return systemDeptMapper.selectList(wrapper); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteDeptTreeService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteDeptTreeService.java new file mode 100644 index 00000000..40e472b2 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteDeptTreeService.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021-2026 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.application.service; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.system.application.model.CuteDeptTreeVo; +import cn.odboy.system.constant.SystemDataScopeEnum; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.model.request.SystemQueryDeptArgs; +import cn.odboy.system.framework.permission.core.KitSecurityHelper; +import cn.odboy.system.service.SystemDeptService; +import cn.odboy.util.KitClassUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class CuteDeptTreeService { + + @Autowired + private SystemDeptService systemDeptService; + + public List listMetadata(SystemQueryDeptArgs args) throws Exception { + String dataScopeType = KitSecurityHelper.getDataScopeType(); + if (dataScopeType.equals(SystemDataScopeEnum.ALL.getValue())) { + args.setPidIsNull(true); + } + List fields = KitClassUtil.getAllFields(args.getClass(), new ArrayList<>()); + List fieldNames = new ArrayList<>() {{ + add("pidIsNull"); + add("enabled"); + }}; + for (Field field : fields) { + // 设置对象的访问权限, 保证对private的属性的访问 + field.setAccessible(true); + Object val = field.get(args); + if (fieldNames.contains(field.getName())) { + continue; + } + if (ObjectUtil.isNotNull(val)) { + args.setPidIsNull(null); + break; + } + } + // 数据权限 + args.setIds(KitSecurityHelper.getCurrentUserDataScope()); + List list = systemDeptService.queryDeptByArgs(args); + // 如果为空, 就代表为自定义权限或者本级权限, 就需要去重, 不理解可以注释掉,看查询结果 + if (StrUtil.isBlank(dataScopeType)) { + return systemDeptService.filterRootDeptList(list).stream().map(this::convertToVo).collect(Collectors.toList()); + } + return list.stream().map(this::convertToVo).collect(Collectors.toList()); + } + + private CuteDeptTreeVo convertToVo(SystemDeptTb record) { + CuteDeptTreeVo treeVo = new CuteDeptTreeVo(); + treeVo.setId(Long.toString(record.getId())); + treeVo.setName(record.getName()); + treeVo.setPid(record.getPid() == null ? null : Long.toString(record.getPid())); + treeVo.setSubCount(record.getSubCount()); + return treeVo; + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteFileUploadService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteFileUploadService.java new file mode 100644 index 00000000..8cdb5934 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteFileUploadService.java @@ -0,0 +1,82 @@ +/* + * Copyright 2021-2026 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.application.service; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.odboy.framework.context.KitRequestHolder; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.server.core.KitFileLocalUploadHelper; +import cn.odboy.system.dal.dataobject.SystemLocalStorageTb; +import cn.odboy.system.dal.mysql.SystemLocalStorageMapper; +import cn.odboy.util.KitFileUtil; +import lombok.extern.slf4j.Slf4j; +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 jakarta.servlet.http.HttpServletRequest; +import java.io.File; +import java.util.Date; + +@Slf4j +@Service +public class CuteFileUploadService { + + @Autowired + private SystemLocalStorageMapper systemLocalStorageMapper; + @Autowired + private KitFileLocalUploadHelper fileUploadPathHelper; + @Autowired + private AppProperties properties; + + @Transactional(rollbackFor = Exception.class) + public String uploadLocal(MultipartFile multipartFile) { + long size = multipartFile.getSize(); + KitFileUtil.checkSize(properties.getOss().getMaxSize(), size); + String suffix = KitFileUtil.getSuffix(multipartFile.getOriginalFilename()); + String type = KitFileUtil.getFileType(suffix); + String uploadDateStr = DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT); + File file = KitFileUtil.upload(multipartFile, fileUploadPathHelper.getPath() + uploadDateStr + File.separator); + if (file == null) { + throw new BadRequestException("上传失败"); + } + try { + String formatSize = KitFileUtil.getSize(size); + String prefixName = KitFileUtil.getPrefix(multipartFile.getOriginalFilename(), null); + 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); + // 构建上传路径 /file/20260117/odboycn-20260117055246428.png + HttpServletRequest httpServletRequest = KitRequestHolder.getHttpServletRequest(); + StringBuffer requestURL = httpServletRequest.getRequestURL(); + String[] splits = requestURL.toString().split("/api/component/CuteFileUpload"); + String requestAddress = splits[0]; + return String.format("%s/file/%s/%s", requestAddress, uploadDateStr, localStorage.getRealName()); + } catch (Exception e) { + log.error("上传文件失败", e); + KitFileUtil.del(file); + throw e; + } + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteProductLineSelectProService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteProductLineSelectProService.java new file mode 100644 index 00000000..c3933524 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteProductLineSelectProService.java @@ -0,0 +1,84 @@ +/* + * Copyright 2021-2026 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.application.service; + +import cn.odboy.system.application.model.CuteProductLineSelectProVo; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +public class CuteProductLineSelectProService extends CuteBaseService { + + /** + * 产品线选择组件Pro版 数据源 + * + * @return / + */ + public List listMetadata() { + List depts = this.listEnabledDept(); + // 查询所有部门并按父子关系组织 + 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) { + CuteProductLineSelectProVo vo = this.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 CuteProductLineSelectProVo 树节点 + */ + private CuteProductLineSelectProVo buildDeptTreePro(SystemDeptTb dept, Map deptMap) { + CuteProductLineSelectProVo vo = new CuteProductLineSelectProVo(); + 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(this.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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteProductLineSelectService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteProductLineSelectService.java new file mode 100644 index 00000000..82301e0b --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteProductLineSelectService.java @@ -0,0 +1,85 @@ +/* + * Copyright 2021-2026 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.application.service; + +import cn.odboy.system.application.model.CuteProductLineSelectVo; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +public class CuteProductLineSelectService extends CuteBaseService { + + /** + * 产品线选择组件 数据源 + * + * @return / + */ + public List listMetadata() { + List depts = this.listEnabledDept(); + return this.buildDeptMetadata(depts); + } + + private List buildDeptMetadata(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<>(); + this.buildDeptIdPath(dept, deptMap, pathIds); + // 反转路径,从顶级部门到当前部门 + Collections.reverse(pathIds); + CuteProductLineSelectVo dto = new CuteProductLineSelectVo(); + dto.setValue(Long.toString(dept.getId())); + dto.setIdPath(pathIds.stream().map(String::valueOf).collect(Collectors.joining("-"))); + // 保留名称路径用于显示 + dto.setLabel(this.buildNamePath(dept, deptMap)); + options.add(dto); + } + // 按部门名称路径排序 + options.sort(Comparator.comparing(CuteProductLineSelectVo::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())) { + this.buildDeptIdPath(deptMap.get(dept.getPid()), deptMap, pathIds); + } + } + + private String buildNamePath(SystemDeptTb dept, Map deptMap) { + List pathNames = new ArrayList<>(); + this.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())) { + this.buildDeptNamePath(deptMap.get(dept.getPid()), deptMap, pathNames); + } + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteUserSelectService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteUserSelectService.java new file mode 100644 index 00000000..9d87d87a --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/application/service/CuteUserSelectService.java @@ -0,0 +1,83 @@ +/* + * Copyright 2021-2026 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.application.service; + +import cn.hutool.core.collection.CollUtil; +import cn.odboy.system.application.model.CuteUserSelectVo; +import cn.odboy.system.dal.dataobject.SystemUserTb; +import cn.odboy.system.dal.model.request.SystemQueryUserArgs; +import cn.odboy.system.dal.mysql.SystemUserMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class CuteUserSelectService { + + @Autowired + private SystemUserMapper systemUserMapper; + + /** + * 模糊查询用户基础信息,限制返回的条数 + * + * @param args 查询参数 + * @return / + */ + public List listMetadata(SystemQueryUserArgs args) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.select(SystemUserTb::getUsername, SystemUserTb::getNickName, SystemUserTb::getDeptId, SystemUserTb::getEmail, SystemUserTb::getPhone); + wrapper.and(c -> { + c.eq(SystemUserTb::getPhone, args.getBlurry()); + c.or(); + c.eq(SystemUserTb::getEmail, args.getBlurry()); + c.or(); + c.like(SystemUserTb::getUsername, args.getBlurry()); + c.or(); + c.like(SystemUserTb::getNickName, args.getBlurry()); + }); + return systemUserMapper.selectPage(new Page<>(1, 50), wrapper).convert(m -> { + CuteUserSelectVo selectVo = new CuteUserSelectVo(); + selectVo.setValue(m.getUsername()); + selectVo.setLabel(m.getNickName()); + selectVo.setDeptId(Long.toString(m.getDeptId())); + selectVo.setEmail(m.getEmail()); + selectVo.setPhone(m.getPhone()); + return selectVo; + }).getRecords(); + } + + public List listMetadataByUsernames(List usernameList) { + if (CollUtil.isEmpty(usernameList)) { + return new ArrayList<>(); + } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.select(SystemUserTb::getUsername, SystemUserTb::getNickName, SystemUserTb::getDeptId, SystemUserTb::getEmail, SystemUserTb::getPhone); + wrapper.in(SystemUserTb::getUsername, usernameList); + return systemUserMapper.selectList(wrapper).stream().map(m -> { + CuteUserSelectVo selectVo = new CuteUserSelectVo(); + selectVo.setValue(m.getUsername()); + selectVo.setLabel(m.getNickName()); + selectVo.setDeptId(Long.toString(m.getDeptId())); + selectVo.setEmail(m.getEmail()); + selectVo.setPhone(m.getPhone()); + return selectVo; + }).collect(Collectors.toList()); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemCacheKey.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemCacheKey.java new file mode 100644 index 00000000..fb51017b --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemCacheKey.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2026 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; + +/** + * 关于缓存的Key集合 + */ +public interface SystemCacheKey { + + /** + * 在线用户 + */ + String ONLINE_USER = "user::token:"; + /** + * 登录验证码 + */ + String CAPTCHA_LOGIN = "captcha::login:"; + /** + * 用户信息 + */ + String USER_INFO = "user::info:"; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemCaptchaBizEnum.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemCaptchaBizEnum.java new file mode 100644 index 00000000..72b455be --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemCaptchaBizEnum.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemDataScopeEnum.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemDataScopeEnum.java new file mode 100644 index 00000000..5eed626d --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemDataScopeEnum.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemTransferProtocolConst.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemTransferProtocolConst.java new file mode 100644 index 00000000..d5c39d84 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemTransferProtocolConst.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2026 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 SystemTransferProtocolConst { + + String PREFIX_HTTP = "http://"; + String PREFIX_HTTPS = "https://"; + String PREFIX_HTTPS_BAD_REQUEST = "外链必须以http://或者https://开头"; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemYesOrNoChConst.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemYesOrNoChConst.java new file mode 100644 index 00000000..eb61d686 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemYesOrNoChConst.java @@ -0,0 +1,22 @@ +/* + * Copyright 2021-2026 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 SystemYesOrNoChConst { + + String YES_STR = "是"; + String NO_STR = "否"; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemZhConst.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemZhConst.java new file mode 100644 index 00000000..67103f77 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/constant/SystemZhConst.java @@ -0,0 +1,24 @@ +/* + * Copyright 2021-2026 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 SystemZhConst { + + String YES_STR = "是"; + String NO_STR = "否"; + String ENABLE_STR = "启用"; + String DISABLE_STR = "停用"; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemAuthController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemAuthController.java new file mode 100644 index 00000000..7ae1c964 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemAuthController.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2026 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.annotation.AnonymousPostMapping; +import cn.odboy.system.dal.model.request.SystemUserLoginArgs; +import cn.odboy.system.dal.model.response.SystemAuthVo; +import cn.odboy.system.service.SystemAuthService; +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.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import jakarta.servlet.http.HttpServletRequest; + +/** + * 授权、根据token获取用户详细信息 + */ +@Slf4j +@RestController +@RequestMapping("/auth") +@Api(tags = "系统:系统授权接口") +public class SystemAuthController { + + @Autowired + private SystemAuthService systemAuthService; + + @ApiOperation("登录授权") + @AnonymousPostMapping(value = "/login") + public ResponseEntity login(@Validated @RequestBody SystemUserLoginArgs loginRequest, HttpServletRequest request) throws Exception { + SystemAuthVo loginInfo = systemAuthService.doLogin(loginRequest, request); + return ResponseEntity.ok(loginInfo); + } + + @ApiOperation("退出登录") + @AnonymousPostMapping(value = "/logout") + public ResponseEntity logout(HttpServletRequest request) { + systemAuthService.doLogout(request); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemCaptchaController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemCaptchaController.java new file mode 100644 index 00000000..f4050b9e --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemCaptchaController.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2026 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.request.SystemCheckEmailCaptchaArgs; +import cn.odboy.system.framework.operalog.OperationLog; +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.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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/captcha") +@Api(tags = "系统:验证码管理") +public class SystemCaptchaController { + + @Autowired + private SystemEmailService systemEmailService; + + @OperationLog + @ApiOperation("重置邮箱, 发送验证码") + @PostMapping(value = "/sendResetEmailCode") + public ResponseEntity sendResetEmailCode(@RequestParam String email) { + systemEmailService.sendCaptcha(SystemCaptchaBizEnum.EMAIL_RESET_EMAIL_CODE, email); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("重置密码,发送验证码") + @PostMapping(value = "/sendResetPasswordCode") + public ResponseEntity sendResetPasswordCode(@RequestParam String email) { + systemEmailService.sendCaptcha(SystemCaptchaBizEnum.EMAIL_RESET_PWD_CODE, email); + return ResponseEntity.ok(null); + } + + @ApiOperation("验证码验证") + @PostMapping(value = "/verifyCode") + public ResponseEntity verifyCode(@Validated @RequestBody SystemCheckEmailCaptchaArgs args) { + systemEmailService.checkEmailCaptcha(args); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDeptController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDeptController.java new file mode 100644 index 00000000..ffe08ca5 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDeptController.java @@ -0,0 +1,100 @@ +/* + * Copyright 2021-2026 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.KitPageArgs; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.model.request.SystemCreateDeptArgs; +import cn.odboy.system.dal.model.request.SystemQueryDeptArgs; +import cn.odboy.system.dal.model.response.SystemDeptVo; +import cn.odboy.system.framework.operalog.OperationLog; +import cn.odboy.system.service.SystemDeptService; +import cn.odboy.util.KitPageUtil; +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.GetMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import jakarta.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Set; + +@RestController +@Api(tags = "系统:部门管理") +@RequestMapping("/api/dept") +public class SystemDeptController { + + @Autowired + private SystemDeptService systemDeptService; + + @OperationLog + @ApiOperation("导出部门数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('dept:list')") + public void exportDept(HttpServletResponse response, SystemQueryDeptArgs args) throws Exception { + systemDeptService.exportDeptXlsx(response, args); + } + + @ApiOperation("查询部门") + @PostMapping(value = "/searchDept") + @PreAuthorize("@el.check('user:list','dept:list')") + public ResponseEntity> queryDept(@Validated @RequestBody KitPageArgs pageArgs) throws Exception { + List depts = systemDeptService.queryAllDeptByArgs(pageArgs.getArgs(), true); + return ResponseEntity.ok(KitPageUtil.toPage(depts)); + } + + @ApiOperation("查询部门:根据ID获取同级与上级数据") + @PostMapping("/searchDeptTree") + @PreAuthorize("@el.check('user:list','dept:list')") + public ResponseEntity> queryDeptSuperiorTree(@RequestBody List ids, @RequestParam(defaultValue = "false") Boolean exclude) { + return ResponseEntity.ok(systemDeptService.searchDeptTree(ids, exclude)); + } + + @OperationLog + @ApiOperation("新增部门") + @PostMapping(value = "/saveDept") + @PreAuthorize("@el.check('dept:add')") + public ResponseEntity saveDept(@Validated @RequestBody SystemCreateDeptArgs args) { + systemDeptService.saveDept(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("修改部门") + @PostMapping(value = "/updateDeptById") + @PreAuthorize("@el.check('dept:edit')") + public ResponseEntity updateDeptById(@Validated(SystemDeptTb.Update.class) @RequestBody SystemDeptTb args) { + systemDeptService.updateDeptById(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("删除部门") + @PostMapping(value = "/deleteDeptByIds") + @PreAuthorize("@el.check('dept:del')") + public ResponseEntity deleteDeptByIds(@RequestBody Set ids) { + systemDeptService.deleteDeptByIds(ids); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictController.java new file mode 100644 index 00000000..1aeb4366 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictController.java @@ -0,0 +1,100 @@ +/* + * Copyright 2021-2026 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.KitPageArgs; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemDictTb; +import cn.odboy.system.dal.model.request.SystemCreateDictArgs; +import cn.odboy.system.dal.model.request.SystemQueryDictArgs; +import cn.odboy.system.framework.operalog.OperationLog; +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 java.util.List; +import java.util.Set; + +import jakarta.servlet.http.HttpServletResponse; +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.GetMapping; +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 +@Api(tags = "系统:字典管理") +@RequestMapping("/api/dict") +public class SystemDictController { + + @Autowired + private SystemDictService systemDictService; + + @OperationLog + @ApiOperation("导出字典数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('dict:list')") + public void exportDict(HttpServletResponse response, SystemQueryDictArgs args) { + systemDictService.exportDictXlsx(response, args); + } + + @Deprecated + @ApiOperation("查询所有字典") + @PostMapping(value = "/queryAllDict") + @PreAuthorize("@el.check('dict:list')") + public ResponseEntity> queryAllDict() { + return ResponseEntity.ok(systemDictService.queryDictByArgs(new SystemQueryDictArgs())); + } + + @ApiOperation("分页查询字典") + @PostMapping(value = "/searchDict") + @PreAuthorize("@el.check('dict:list')") + public ResponseEntity> queryDictByArgs(@Validated @RequestBody KitPageArgs pageArgs) { + Page page = new Page<>(pageArgs.getPage(), pageArgs.getSize()); + return ResponseEntity.ok(systemDictService.searchDict(pageArgs.getArgs(), page, pageArgs.getOrderBy())); + } + + @OperationLog + @ApiOperation("新增字典") + @PostMapping(value = "/saveDict") + @PreAuthorize("@el.check('dict:add')") + public ResponseEntity saveDict(@Validated @RequestBody SystemCreateDictArgs args) { + systemDictService.saveDict(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("修改字典") + @PostMapping(value = "/updateDictById") + @PreAuthorize("@el.check('dict:edit')") + public ResponseEntity updateDictById(@Validated(SystemDictTb.Update.class) @RequestBody SystemDictTb args) { + systemDictService.updateDictById(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("删除字典") + @PostMapping(value = "/deleteDictByIds") + @PreAuthorize("@el.check('dict:del')") + public ResponseEntity deleteDictByIds(@RequestBody Set ids) { + systemDictService.deleteDictByIds(ids); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictDetailController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictDetailController.java new file mode 100644 index 00000000..9f813f79 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemDictDetailController.java @@ -0,0 +1,79 @@ +/* + * Copyright 2021-2026 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.KitPageArgs; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemDictDetailTb; +import cn.odboy.system.dal.model.request.SystemCreateDictDetailArgs; +import cn.odboy.system.dal.model.request.SystemQueryDictDetailArgs; +import cn.odboy.system.dal.model.response.SystemDictDetailVo; +import cn.odboy.system.framework.operalog.OperationLog; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Api(tags = "系统:字典详情管理") +@RequestMapping("/api/dictDetail") +public class SystemDictDetailController { + + @Autowired + private SystemDictDetailService systemDictDetailService; + + @ApiOperation("查询字典详情") + @PostMapping(value = "/searchDictDetail") + public ResponseEntity> queryDictDetailByArgs(@Validated @RequestBody KitPageArgs pageArgs) { + Page page = new Page<>(pageArgs.getPage(), pageArgs.getSize()); + return ResponseEntity.ok(systemDictDetailService.searchDictDetail(pageArgs.getArgs(), page)); + } + + @OperationLog + @ApiOperation("新增字典详情") + @PostMapping(value = "/saveDictDetail") + @PreAuthorize("@el.check('dict:add')") + public ResponseEntity saveDictDetail(@Validated @RequestBody SystemCreateDictDetailArgs args) { + systemDictDetailService.saveDictDetail(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("修改字典详情") + @PostMapping(value = "/updateDictDetailById") + @PreAuthorize("@el.check('dict:edit')") + public ResponseEntity updateDictDetailById(@Validated(SystemDictDetailTb.Update.class) @RequestBody SystemDictDetailTb args) { + systemDictDetailService.updateDictDetailById(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("删除字典详情") + @PostMapping(value = "/deleteDictDetailById") + @PreAuthorize("@el.check('dict:del')") + public ResponseEntity deleteDictDetailById(@RequestBody SystemDictDetailTb args) { + systemDictDetailService.deleteDictDetailById(args.getId()); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemEmailController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemEmailController.java new file mode 100644 index 00000000..445fba64 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemEmailController.java @@ -0,0 +1,64 @@ +/* + * Copyright 2021-2026 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.request.SystemSendEmailArgs; +import cn.odboy.system.framework.operalog.OperationLog; +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.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()); + } + + @OperationLog + @ApiOperation("修改邮箱配置") + @PostMapping(value = "/updateEmailConfigById") + public ResponseEntity updateEmailConfigById(@Validated @RequestBody SystemEmailConfigTb emailConfig) throws Exception { + systemEmailService.updateEmailConfigById(emailConfig); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("发送邮件") + @PostMapping(value = "/send") + public ResponseEntity send(@Validated @RequestBody SystemSendEmailArgs sendEmailRequest) { + systemEmailService.sendEmail(sendEmailRequest); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemJobController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemJobController.java new file mode 100644 index 00000000..f0364ecc --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemJobController.java @@ -0,0 +1,90 @@ +/* + * Copyright 2021-2026 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.KitPageArgs; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemJobTb; +import cn.odboy.system.dal.model.request.SystemCreateJobArgs; +import cn.odboy.system.dal.model.request.SystemQueryJobArgs; +import cn.odboy.system.framework.operalog.OperationLog; +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.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +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 jakarta.servlet.http.HttpServletResponse; +import java.util.Set; + +@RestController +@Api(tags = "系统:岗位管理") +@RequestMapping("/api/job") +public class SystemJobController { + + @Autowired + private SystemJobService systemJobService; + + @OperationLog + @ApiOperation("导出岗位数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('job:list')") + public void exportJob(HttpServletResponse response, SystemQueryJobArgs args) { + systemJobService.exportJobXlsx(response, args); + } + + @ApiOperation("分页查询岗位") + @PostMapping(value = "/searchJob") + @PreAuthorize("@el.check('job:list','user:list')") + public ResponseEntity> queryJobByArgs(@Validated @RequestBody KitPageArgs pageArgs) { + Page page = new Page<>(pageArgs.getPage(), pageArgs.getSize()); + return ResponseEntity.ok(systemJobService.searchJobByArgs(pageArgs.getArgs(), page)); + } + + @OperationLog + @ApiOperation("新增岗位") + @PostMapping(value = "/saveJob") + @PreAuthorize("@el.check('job:add')") + public ResponseEntity saveJob(@Validated @RequestBody SystemCreateJobArgs args) { + systemJobService.saveJob(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("修改岗位") + @PostMapping(value = "/updateJobById") + @PreAuthorize("@el.check('job:edit')") + public ResponseEntity updateJobById(@Validated(SystemJobTb.Update.class) @RequestBody SystemJobTb args) { + systemJobService.updateJobById(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("删除岗位") + @PostMapping(value = "/deleteJobByIds") + @PreAuthorize("@el.check('job:del')") + public ResponseEntity deleteJobByIds(@RequestBody Set ids) { + systemJobService.deleteJobByIds(ids); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemLocalStorageController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemLocalStorageController.java new file mode 100644 index 00000000..c55c1b94 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemLocalStorageController.java @@ -0,0 +1,97 @@ +/* + * Copyright 2021-2026 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.KitPageArgs; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemLocalStorageTb; +import cn.odboy.system.dal.model.request.SystemQueryStorageArgs; +import cn.odboy.system.framework.operalog.OperationLog; +import cn.odboy.system.service.SystemLocalStorageService; +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.GetMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import jakarta.servlet.http.HttpServletResponse; + +@RestController +@Api(tags = "工具:本地存储管理") +@RequestMapping("/api/localStorage") +public class SystemLocalStorageController { + + @Autowired + private SystemLocalStorageService localStorageService; + + @ApiOperation("查询文件") + @PostMapping(value = "/searchLocalStorage") + @PreAuthorize("@el.check('storage:list')") + public ResponseEntity> queryLocalStorage(@Validated @RequestBody KitPageArgs pageArgs) { + Page page = new Page<>(pageArgs.getPage(), pageArgs.getSize()); + return ResponseEntity.ok(localStorageService.searchLocalStorage(pageArgs.getArgs(), page)); + } + + @OperationLog + @ApiOperation("导出本地存储记录数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('storage:list')") + public void exportFile(HttpServletResponse response, SystemQueryStorageArgs args) { + localStorageService.exportLocalStorageXlsx(response, args); + } + + @OperationLog + @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); + } + + @OperationLog + @ApiOperation("上传图片") + @PostMapping("/uploadPicture") + public ResponseEntity uploadPicture(@RequestParam MultipartFile file) { + SystemLocalStorageTb localStorage = localStorageService.uploadPicture(file); + return ResponseEntity.ok(localStorage); + } + + @OperationLog + @ApiOperation("修改文件") + @PostMapping(value = "/updateLocalStorageById") + @PreAuthorize("@el.check('storage:edit')") + public ResponseEntity updateLocalStorageById(@Validated @RequestBody SystemLocalStorageTb args) { + localStorageService.updateLocalStorageById(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("删除文件") + @PostMapping(value = "/deleteFileByIds") + public ResponseEntity deleteFileByIds(@RequestBody Long[] ids) { + localStorageService.deleteFileByIds(ids); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMenuController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMenuController.java new file mode 100644 index 00000000..55d22beb --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMenuController.java @@ -0,0 +1,133 @@ +/* + * Copyright 2021-2026 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.KitPageArgs; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemMenuTb; +import cn.odboy.system.dal.model.request.SystemQueryMenuArgs; +import cn.odboy.system.dal.model.response.SystemMenuRouterVo; +import cn.odboy.system.dal.model.response.SystemMenuVo; +import cn.odboy.system.framework.operalog.OperationLog; +import cn.odboy.system.service.SystemMenuService; +import cn.odboy.util.KitPageUtil; +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.GetMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import jakarta.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Set; + +@RestController +@Api(tags = "系统:菜单管理") +@RequestMapping("/api/menu") +public class SystemMenuController { + + @Autowired + private SystemMenuService systemMenuService; + + @OperationLog + @ApiOperation("导出菜单数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('menu:list')") + public void exportMenu(HttpServletResponse response, SystemQueryMenuArgs args) throws Exception { + systemMenuService.exportMenuXlsx(response, args); + } + + @PostMapping(value = "/buildMenus") + @ApiOperation("获取前端所需菜单") + public ResponseEntity> buildMenus() { + List systemMenuRouterVos = systemMenuService.buildFrontMenus(); + return ResponseEntity.ok(systemMenuRouterVos); + } + + @ApiOperation("返回全部的菜单") + @PostMapping(value = "/listMenuByPid") + @PreAuthorize("@el.check('menu:list','roles:list')") + public ResponseEntity> listMenuByPid(@RequestParam Long pid) { + List systemMenuTbs = systemMenuService.listMenuByPid(pid, false); + return ResponseEntity.ok(systemMenuTbs); + } + + @ApiOperation("返回全部的菜单") + @PostMapping(value = "/listRoleMenuByPid") + @PreAuthorize("@el.check('menu:list','roles:list')") + public ResponseEntity> listRoleMenuByPid(@RequestParam Long pid) { + List systemMenuTbs = systemMenuService.listMenuByPid(pid, true); + return ResponseEntity.ok(systemMenuTbs); + } + + @ApiOperation("根据菜单ID返回所有子节点ID, 包含自身ID") + @PostMapping(value = "/listChildMenuSetByMenuId") + @PreAuthorize("@el.check('menu:list','roles:list')") + public ResponseEntity> listChildMenuSetByMenuId(@RequestParam Long id) { + Set ids = systemMenuService.listChildMenuSetByMenuId(id); + return ResponseEntity.ok(ids); + } + + @PostMapping(value = "/searchMenu") + @ApiOperation("查询菜单") + @PreAuthorize("@el.check('menu:list')") + public ResponseEntity> queryAllMenu( + @Validated @RequestBody KitPageArgs pageArgs) throws Exception { + List menuList = systemMenuService.queryAllMenu(pageArgs.getArgs(), true); + return ResponseEntity.ok(KitPageUtil.toPage(menuList)); + } + + @ApiOperation("查询菜单:根据ID获取同级与上级数据") + @PostMapping("/queryMenuSuperior") + @PreAuthorize("@el.check('menu:list')") + public ResponseEntity> queryMenuSuperior(@RequestBody List ids) { + List systemMenuTbs = systemMenuService.listMenuSuperior(ids); + return ResponseEntity.ok(systemMenuTbs); + } + + @OperationLog + @ApiOperation("新增菜单") + @PostMapping(value = "/saveMenu") + @PreAuthorize("@el.check('menu:add')") + public ResponseEntity saveMenu(@Validated @RequestBody SystemMenuTb args) { + systemMenuService.saveMenu(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("修改菜单") + @PostMapping(value = "/updateMenuById") + @PreAuthorize("@el.check('menu:edit')") + public ResponseEntity updateMenuById(@Validated(SystemMenuTb.Update.class) @RequestBody SystemMenuTb args) { + systemMenuService.updateMenuById(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("删除菜单") + @PostMapping(value = "/deleteMenuByIds") + @PreAuthorize("@el.check('menu:del')") + public ResponseEntity deleteMenuByIds(@RequestBody Set ids) { + systemMenuService.deleteMenuByIds(ids); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMonitorController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMonitorController.java new file mode 100644 index 00000000..b29d8d26 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemMonitorController.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2026 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.framework.operalog.OperationLog; +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; + + @OperationLog + @PostMapping(value = "/getServerMonitorInfo") + @ApiOperation("查询服务监控") + @PreAuthorize("@el.check('monitor:list')") + public ResponseEntity> getServerMonitorInfo() { + return ResponseEntity.ok(systemMonitorService.getServerMonitorInfo()); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOnlineController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOnlineController.java new file mode 100644 index 00000000..39798607 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOnlineController.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021-2026 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.KitPageArgs; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.model.response.SystemUserOnlineVo; +import cn.odboy.system.dal.redis.SystemUserOnlineInfoDAO; +import cn.odboy.system.framework.operalog.OperationLog; +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.GetMapping; +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 jakarta.servlet.http.HttpServletResponse; +import java.util.Set; + +@RestController +@RequestMapping("/api/user/online") +@Api(tags = "系统:在线用户管理") +public class SystemOnlineController { + + @Autowired + private SystemUserOnlineInfoDAO systemUserOnlineInfoDAO; + + @OperationLog + @ApiOperation("查询在线用户") + @PostMapping(value = "/searchOnlineUser") + @PreAuthorize("@el.check()") + public ResponseEntity> queryOnlineUser(@Validated @RequestBody KitPageArgs pageArgs) { + IPage page = new Page<>(pageArgs.getPage(), pageArgs.getSize()); + return ResponseEntity.ok(systemUserOnlineInfoDAO.searchOnlineUser(pageArgs.getArgs(), page)); + } + + @OperationLog + @ApiOperation("导出在线用户数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check()") + public void exportOnlineUser(HttpServletResponse response, String username) { + systemUserOnlineInfoDAO.exportOnlineUserXlsx(response, username); + } + + @OperationLog + @ApiOperation("踢出在线用户") + @PostMapping(value = "/kickOutUser") + @PreAuthorize("@el.check()") + public ResponseEntity kickOutUser(@RequestBody Set keys) throws Exception { + systemUserOnlineInfoDAO.kickOutUser(keys); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOperationLogController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOperationLogController.java new file mode 100644 index 00000000..aec892b9 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOperationLogController.java @@ -0,0 +1,50 @@ +/* + * Copyright 2021-2026 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.KitPageArgs; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemOperationLogTb; +import cn.odboy.system.dal.model.request.SystemQueryOperationLogArgs; +import cn.odboy.system.service.SystemOperationLogService; +import cn.odboy.util.KitPageUtil; +import com.baomidou.mybatisplus.core.metadata.IPage; +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 +@Api(tags = "系统:审计日志") +@RequestMapping("/api/logs") +public class SystemOperationLogController { + + @Autowired + private SystemOperationLogService systemOperationLogService; + + @PostMapping(value = "/searchUserLog") + @ApiOperation("用户日志查询") + public ResponseEntity> searchUserLog(@Validated @RequestBody KitPageArgs pageArgs) { + IPage systemOperationLogTbPage = systemOperationLogService.searchUserLog(pageArgs); + return new ResponseEntity<>(KitPageUtil.toPage(systemOperationLogTbPage), HttpStatus.OK); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOssStorageController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOssStorageController.java new file mode 100644 index 00000000..34b1bf84 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemOssStorageController.java @@ -0,0 +1,83 @@ +/* + * Copyright 2021-2026 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.KitPageArgs; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemOssStorageTb; +import cn.odboy.system.dal.model.request.SystemQueryStorageArgs; +import cn.odboy.system.dal.model.response.SystemOssStorageVo; +import cn.odboy.system.framework.operalog.OperationLog; +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.GetMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import jakarta.servlet.http.HttpServletResponse; + +@Slf4j +@RestController +@Api(tags = "工具:OSS存储管理") +@RequestMapping("/api/ossStorage") +public class SystemOssStorageController { + + @Autowired + private SystemOssStorageService systemOssStorageService; + + @ApiOperation("查询文件") + @PostMapping(value = "/searchOssStorage") + @PreAuthorize("@el.check('storage:list')") + public ResponseEntity> queryOssStorage(@Validated @RequestBody KitPageArgs pageArgs) { + Page page = new Page<>(pageArgs.getPage(), pageArgs.getSize()); + return ResponseEntity.ok(systemOssStorageService.searchOssStorage(pageArgs.getArgs(), page)); + } + + @OperationLog + @ApiOperation("导出OSS存储记录数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('storage:list')") + public void exportFile(HttpServletResponse response, SystemQueryStorageArgs args) { + systemOssStorageService.exportOssStorageXlsx(response, args); + } + + @OperationLog + @ApiOperation("上传文件到OSS") + @PostMapping(value = "/uploadFile") + @PreAuthorize("@el.check('storage:add')") + public ResponseEntity uploadFile(@RequestParam("file") MultipartFile file) { + String fileUrl = systemOssStorageService.uploadFile(file); + return ResponseEntity.ok(fileUrl); + } + + @OperationLog + @ApiOperation("从OSS删除文件") + @PostMapping(value = "/deleteFileByIds") + public ResponseEntity deleteFileByIds(@RequestBody Long[] ids) { + systemOssStorageService.deleteFileByIds(ids); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemQuartzJobController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemQuartzJobController.java new file mode 100644 index 00000000..5e19e0c0 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemQuartzJobController.java @@ -0,0 +1,130 @@ +/* + * Copyright 2021-2026 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.KitPageArgs; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemQuartzJobTb; +import cn.odboy.system.dal.dataobject.SystemQuartzLogTb; +import cn.odboy.system.dal.model.request.SystemQueryQuartzJobArgs; +import cn.odboy.system.dal.model.request.SystemUpdateQuartzJobArgs; +import cn.odboy.system.framework.operalog.OperationLog; +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.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +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 jakarta.servlet.http.HttpServletResponse; +import java.util.Set; + +@Slf4j +@RestController +@RequestMapping("/api/quartzJob") +@Api(tags = "系统:定时任务管理") +public class SystemQuartzJobController { + + @Autowired + private SystemQuartzJobService systemQuartzJobService; + + @ApiOperation("查询定时任务") + @PostMapping(value = "/searchQuartzJob") + @PreAuthorize("@el.check('quartzJob:list')") + public ResponseEntity> queryQuartzJobByCrud(@Validated @RequestBody KitPageArgs pageArgs) { + Page page = new Page<>(pageArgs.getPage(), pageArgs.getSize()); + return ResponseEntity.ok(systemQuartzJobService.searchQuartzJobByArgs(pageArgs.getArgs(), page)); + } + + @OperationLog + @ApiOperation("导出定时任务数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('quartzJob:list')") + public void exportQuartzJob(HttpServletResponse response, SystemQueryQuartzJobArgs args) { + systemQuartzJobService.exportQuartzJobXlsx(response, args); + } + + @OperationLog + @ApiOperation("导出定时任务日志数据") + @GetMapping(value = "/logs/download") + @PreAuthorize("@el.check('quartzJob:list')") + public void exportQuartzJobLog(HttpServletResponse response, SystemQueryQuartzJobArgs args) { + systemQuartzJobService.exportQuartzLogXlsx(response, args); + } + + @ApiOperation("查询定时任务执行日志") + @PostMapping(value = "/searchQuartzLog") + @PreAuthorize("@el.check('quartzJob:list')") + public ResponseEntity> queryQuartzJobLog(@Validated @RequestBody KitPageArgs pageArgs) { + Page page = new Page<>(pageArgs.getPage(), pageArgs.getSize()); + return ResponseEntity.ok(systemQuartzJobService.searchQuartzLogByArgs(pageArgs.getArgs(), page)); + } + + @OperationLog + @ApiOperation("新增定时任务") + @PostMapping(value = "/createQuartzJob") + @PreAuthorize("@el.check('quartzJob:add')") + public ResponseEntity createQuartzJob(@Validated @RequestBody SystemQuartzJobTb args) { + systemQuartzJobService.createJob(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("修改定时任务") + @PostMapping(value = "/updateQuartzJobResumeCron") + @PreAuthorize("@el.check('quartzJob:edit')") + public ResponseEntity updateQuartzJob(@Validated(SystemQuartzJobTb.Update.class) @RequestBody SystemUpdateQuartzJobArgs args) { + systemQuartzJobService.updateQuartzJobResumeCron(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("更改定时任务状态") + @PostMapping(value = "/switchQuartzJobStatus/{id}") + @PreAuthorize("@el.check('quartzJob:edit')") + public ResponseEntity switchQuartzJobStatus(@PathVariable Long id) { + SystemQuartzJobTb quartzJobTb = systemQuartzJobService.getQuartzJobById(id); + systemQuartzJobService.switchQuartzJobStatus(quartzJobTb); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("执行定时任务") + @PostMapping(value = "/startQuartzJob/{id}") + @PreAuthorize("@el.check('quartzJob:edit')") + public ResponseEntity startQuartzJob(@PathVariable Long id) { + SystemQuartzJobTb quartzJobTb = systemQuartzJobService.getQuartzJobById(id); + systemQuartzJobService.startQuartzJob(quartzJobTb); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("删除定时任务") + @PostMapping(value = "/deleteJobByIds") + @PreAuthorize("@el.check('quartzJob:del')") + public ResponseEntity deleteJobByIds(@RequestBody Set ids) { + systemQuartzJobService.deleteJobByIds(ids); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemRoleController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemRoleController.java new file mode 100644 index 00000000..4976e91b --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemRoleController.java @@ -0,0 +1,121 @@ +/* + * Copyright 2021-2026 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.KitPageArgs; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import cn.odboy.system.dal.model.request.SystemCreateRoleArgs; +import cn.odboy.system.dal.model.request.SystemQueryRoleArgs; +import cn.odboy.system.dal.model.response.SystemRoleVo; +import cn.odboy.system.framework.operalog.OperationLog; +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.GetMapping; +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 jakarta.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Set; + +@RestController +@Api(tags = "系统:角色管理") +@RequestMapping("/api/role") +public class SystemRoleController { + + @Autowired + private SystemRoleService systemRoleService; + + @ApiOperation("获取单个role") + @PostMapping(value = "/getRoleById") + @PreAuthorize("@el.check('roles:list')") + public ResponseEntity getRoleById(@RequestBody SystemRoleTb args) { + return ResponseEntity.ok(systemRoleService.getRoleVoById(args.getId())); + } + + @ApiOperation("导出角色数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('role:list')") + public void exportRole(HttpServletResponse response, SystemQueryRoleArgs args) { + systemRoleService.exportRoleXlsx(response, args); + } + + @ApiOperation("返回全部的角色") + @PostMapping(value = "/listAllRole") + @PreAuthorize("@el.check('roles:list','user:add','user:edit')") + public ResponseEntity> listAllRole() { + return ResponseEntity.ok(systemRoleService.listAllRole()); + } + + @ApiOperation("查询角色") + @PostMapping(value = "/searchRole") + @PreAuthorize("@el.check('roles:list')") + public ResponseEntity> queryRoleByArgs(@Validated @RequestBody KitPageArgs pageArgs) { + Page page = new Page<>(pageArgs.getPage(), pageArgs.getSize()); + return ResponseEntity.ok(systemRoleService.searchRoleByArgs(pageArgs.getArgs(), page)); + } + + @ApiOperation("获取用户级别") + @PostMapping(value = "/getCurrentUserRoleLevel") + public ResponseEntity getCurrentUserRoleLevel() { + return ResponseEntity.ok(systemRoleService.getCurrentUserRoleLevel()); + } + + @OperationLog + @ApiOperation("新增角色") + @PostMapping(value = "/saveRole") + @PreAuthorize("@el.check('roles:add')") + public ResponseEntity saveRole(@Validated @RequestBody SystemCreateRoleArgs args) { + systemRoleService.saveRole(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("修改角色") + @PostMapping(value = "/updateRoleById") + @PreAuthorize("@el.check('roles:edit')") + public ResponseEntity updateRoleById(@Validated(SystemRoleTb.Update.class) @RequestBody SystemRoleVo args) { + systemRoleService.updateRoleById(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("修改角色菜单") + @PostMapping(value = "/updateBindMenuById") + @PreAuthorize("@el.check('roles:edit')") + public ResponseEntity updateBindMenuById(@RequestBody SystemRoleVo args) { + systemRoleService.updateBindMenuById(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("删除角色") + @PostMapping(value = "/deleteRoleByIds") + @PreAuthorize("@el.check('roles:del')") + public ResponseEntity deleteRoleByIds(@RequestBody Set ids) { + systemRoleService.deleteRoleByIds(ids); + return ResponseEntity.ok(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemUserController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemUserController.java new file mode 100644 index 00000000..a33fc5c4 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/controller/SystemUserController.java @@ -0,0 +1,149 @@ +/* + * Copyright 2021-2026 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.KitPageArgs; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemUserTb; +import cn.odboy.system.dal.model.request.SystemQueryUserArgs; +import cn.odboy.system.dal.model.request.SystemUpdateUserPasswordArgs; +import cn.odboy.system.dal.model.response.SystemUserJwtVo; +import cn.odboy.system.dal.model.response.SystemUserVo; +import cn.odboy.system.framework.operalog.OperationLog; +import cn.odboy.system.framework.permission.core.KitSecurityHelper; +import cn.odboy.system.service.SystemAuthService; +import cn.odboy.system.service.SystemUserService; +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.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Set; + +@Api(tags = "系统:用户管理") +@RestController +@RequestMapping("/api/user") +public class SystemUserController { + + @Autowired + private SystemUserService systemUserService; + @Autowired + private SystemAuthService systemAuthService; + + @OperationLog + @ApiOperation("导出用户数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('user:list')") + public void exportUserExcel(HttpServletResponse response, SystemQueryUserArgs args) { + systemUserService.exportUserXlsx(response, args); + } + + @ApiOperation("查询用户") + @PostMapping(value = "/aggregationSearchUser") + @PreAuthorize("@el.check('user:list')") + public ResponseEntity> queryUserByArgs(@Validated @RequestBody KitPageArgs pageArgs) { + String currentUsername = KitSecurityHelper.getCurrentUsername(); + Page page = new Page<>(pageArgs.getPage(), pageArgs.getSize()); + return ResponseEntity.ok(systemUserService.aggregationSearchUserByArgs(page, pageArgs.getArgs(), currentUsername)); + } + + @OperationLog + @ApiOperation("新增用户") + @PostMapping(value = "/saveUser") + @PreAuthorize("@el.check('user:add')") + public ResponseEntity saveUser(@Validated @RequestBody SystemUserVo args) { + systemUserService.saveUser(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("修改用户数据") + @PostMapping(value = "/updateUserById") + @PreAuthorize("@el.check('user:edit')") + public ResponseEntity updateUserById(@Validated(SystemUserTb.Update.class) @RequestBody SystemUserVo args) { + systemUserService.updateUserById(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("修改个人中心信息") + @PostMapping(value = "/updateUserCenterInfoById") + public ResponseEntity updateUserCenterInfoById(@Validated(SystemUserTb.Update.class) @RequestBody SystemUserTb args) { + systemUserService.updateUserCenterInfoById(args); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("删除用户") + @PostMapping(value = "/deleteUserByIds") + @PreAuthorize("@el.check('user:del')") + public ResponseEntity deleteUserByIds(@RequestBody Set ids) { + systemUserService.deleteUserByIds(ids); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("修改用户密码") + @PostMapping(value = "/updateUserPasswordByUsername") + public ResponseEntity updateUserPasswordByUsername(@RequestBody SystemUpdateUserPasswordArgs passVo) throws Exception { + String currentUsername = KitSecurityHelper.getCurrentUsername(); + systemUserService.updateUserPasswordByUsername(currentUsername, passVo); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("重置用户密码") + @PostMapping(value = "/resetUserPasswordByIds") + public ResponseEntity resetUserPasswordByIds(@RequestBody Set ids) { + systemUserService.resetUserPasswordByIds(ids); + return ResponseEntity.ok(null); + } + + @OperationLog + @ApiOperation("修改用户头像") + @PostMapping(value = "/updateUserAvatar") + public ResponseEntity updateUserAvatar(@RequestParam MultipartFile avatar) { + return ResponseEntity.ok(systemUserService.updateUserAvatar(avatar)); + } + + @OperationLog + @ApiOperation("修改用户邮箱") + @PostMapping(value = "/updateUserEmailByUsername/{code}") + public ResponseEntity updateUserEmailByUsername(@PathVariable String code, @RequestBody SystemUserTb args) throws Exception { + String currentUsername = KitSecurityHelper.getCurrentUsername(); + systemUserService.updateUserEmailByUsername(currentUsername, args, code); + return ResponseEntity.ok(null); + } + + + @ApiOperation("获取用户信息") + @PostMapping(value = "/getCurrentUserInfo") + public ResponseEntity getUserInfo() { + SystemUserJwtVo userInfoVo = systemAuthService.getCurrentUserInfo(); + return ResponseEntity.ok(userInfoVo); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDeptTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDeptTb.java new file mode 100644 index 00000000..04f92d14 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDeptTb.java @@ -0,0 +1,82 @@ +/* + * Copyright 2021-2026 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.KitBaseUserTimeTb; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; +import jakarta.validation.constraints.NotNull; +import java.util.Objects; + +@Getter +@Setter +@TableName("system_dept") +public class SystemDeptTb extends KitBaseUserTimeTb { + + @NotNull(groups = Update.class) + @TableId(value = "dept_id", type = IdType.AUTO) + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + @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; + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictDetailTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictDetailTb.java new file mode 100644 index 00000000..11dc5427 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictDetailTb.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2026 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.KitBaseUserTimeTb; +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 jakarta.validation.constraints.NotNull; + +@Getter +@Setter +@TableName("system_dict_detail") +public class SystemDictDetailTb extends KitBaseUserTimeTb { + + @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; + @ApiModelProperty(value = "字典标签") + private String label; + @ApiModelProperty(value = "字典值") + private String value; + @ApiModelProperty(value = "排序") + private Integer dictSort = 999; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictTb.java new file mode 100644 index 00000000..50b6c8fd --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemDictTb.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2026 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.KitBaseUserTimeTb; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +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 jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +@Getter +@Setter +@TableName("system_dict") +public class SystemDictTb extends KitBaseUserTimeTb { + + @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; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemEmailConfigTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemEmailConfigTb.java new file mode 100644 index 00000000..b91b65ad --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemEmailConfigTb.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2026 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.KitObject; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.NotBlank; + +/** + * 邮件配置类,数据存覆盖式存入数据存 + */ +@Data +@TableName("system_email_config") +@EqualsAndHashCode(callSuper = false) +public class SystemEmailConfigTb extends KitObject { + + @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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemJobTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemJobTb.java new file mode 100644 index 00000000..758a05b4 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemJobTb.java @@ -0,0 +1,64 @@ +/* + * Copyright 2021-2026 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.KitBaseUserTimeTb; +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 jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.Objects; + +@Getter +@Setter +@TableName("system_job") +public class SystemJobTb extends KitBaseUserTimeTb { + + @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); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemLocalStorageTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemLocalStorageTb.java new file mode 100644 index 00000000..b89b097c --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemLocalStorageTb.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2026 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.KitBaseUserTimeTb; +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 KitBaseUserTimeTb { + + @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; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemMenuTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemMenuTb.java new file mode 100644 index 00000000..527084fe --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemMenuTb.java @@ -0,0 +1,97 @@ +/* + * Copyright 2021-2026 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.KitBaseUserTimeTb; +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 jakarta.validation.constraints.NotNull; +import java.util.Objects; + +@Getter +@Setter +@TableName("system_menu") +public class SystemMenuTb extends KitBaseUserTimeTb { + + @NotNull(groups = {Update.class}) + @TableId(value = "menu_id", type = IdType.AUTO) + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + @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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOperationLogTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOperationLogTb.java new file mode 100644 index 00000000..d24e9967 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOperationLogTb.java @@ -0,0 +1,102 @@ +/* + * Copyright 2021-2026 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.KitObject; +import com.baomidou.mybatisplus.annotation.FieldFill; +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; +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 KitObject { + + /** + * 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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOssStorageTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOssStorageTb.java new file mode 100644 index 00000000..b6f9c3f3 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemOssStorageTb.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021-2026 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.KitBaseUserTimeTb; +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 KitBaseUserTimeTb { + + @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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzJobTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzJobTb.java new file mode 100644 index 00000000..ee0ab848 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzJobTb.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021-2026 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.KitBaseUserTimeTb; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +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 jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +@Getter +@Setter +@TableName("system_quartz_job") +public class SystemQuartzJobTb extends KitBaseUserTimeTb { + + public static final String JOB_KEY = "JOB_KEY"; + @TableId(value = "id", type = IdType.AUTO) + @NotNull(groups = {Update.class}) + private Long id; + @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; + /** + * 扩展字段 + */ + @TableField(exist = false) + @ApiModelProperty(value = "用于子任务唯一标识", hidden = true) + private String uuid; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzLogTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzLogTb.java new file mode 100644 index 00000000..b0790f6c --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemQuartzLogTb.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2026 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.KitObject; +import com.baomidou.mybatisplus.annotation.FieldFill; +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.Data; +import lombok.EqualsAndHashCode; +import java.util.Date; + +@Data +@TableName("system_quartz_log") +@EqualsAndHashCode(callSuper = false) +public class SystemQuartzLogTb extends KitObject { + + @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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleDeptTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleDeptTb.java new file mode 100644 index 00000000..0015432f --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleDeptTb.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2026 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.KitObject; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@TableName("system_roles_depts") +public class SystemRoleDeptTb extends KitObject { + + @TableField(value = "role_id") + private Long roleId; + @TableField(value = "dept_id") + private Long deptId; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleMenuTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleMenuTb.java new file mode 100644 index 00000000..0f35f183 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleMenuTb.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2026 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 com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +/** + * 角色菜单 + */ +@Getter +@Setter +@TableName("system_roles_menus") +public class SystemRoleMenuTb { + + @TableField(value = "role_id") + private Long roleId; + @TableField(value = "menu_id") + private Long menuId; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleTb.java new file mode 100644 index 00000000..890e0e9d --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemRoleTb.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021-2026 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.KitBaseUserTimeTb; +import cn.odboy.system.constant.SystemDataScopeEnum; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +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 jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.Objects; + +/** + * 角色 + */ +@Getter +@Setter +@TableName("system_role") +public class SystemRoleTb extends KitBaseUserTimeTb { + + @NotNull(groups = {Update.class}) + @TableId(value = "role_id", type = IdType.AUTO) + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + @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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserJobTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserJobTb.java new file mode 100644 index 00000000..68d9b3bd --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserJobTb.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2026 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 com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +/** + * 角色菜单 + */ +@Getter +@Setter +@TableName("system_users_jobs") +public class SystemUserJobTb { + + @TableField(value = "user_id") + private Long userId; + @TableField(value = "job_id") + private Long jobId; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserRoleTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserRoleTb.java new file mode 100644 index 00000000..d07ce33e --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserRoleTb.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2026 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.KitObject; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@TableName("system_users_roles") +public class SystemUserRoleTb extends KitObject { + + @TableField(value = "user_id") + private Long userId; + @TableField(value = "role_id") + private Long roleId; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserTb.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserTb.java new file mode 100644 index 00000000..8b022468 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/dataobject/SystemUserTb.java @@ -0,0 +1,100 @@ +/* + * Copyright 2021-2026 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.KitBaseUserTimeTb; +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 jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.Date; +import java.util.Objects; + +@Getter +@Setter +@TableName("system_user") +public class SystemUserTb extends KitBaseUserTimeTb { + + @NotNull(groups = Update.class) + @TableId(value = "user_id", type = IdType.AUTO) + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + @TableField(value = "dept_id") + @ApiModelProperty(hidden = true) + private Long deptId; + @NotBlank + @ApiModelProperty(value = "用户名称") + @TableField(value = "username") + private String username; + @NotBlank + @ApiModelProperty(value = "用户昵称") + @TableField(value = "nick_name") + private String nickName; + @Email + @NotBlank + @ApiModelProperty(value = "邮箱") + @TableField(value = "email") + private String email; + @NotBlank + @ApiModelProperty(value = "电话号码") + @TableField(value = "phone") + private String phone; + @ApiModelProperty(value = "用户性别") + @TableField(value = "gender") + private String gender; + @ApiModelProperty(value = "头像真实名称", hidden = true) + @TableField(value = "avatar_name") + private String avatarName; + @ApiModelProperty(value = "头像存储的路径", hidden = true) + @TableField(value = "avatar_path") + private String avatarPath; + @ApiModelProperty(value = "密码") + @TableField(value = "password") + private String password; + @NotNull + @ApiModelProperty(value = "是否启用") + @TableField(value = "enabled") + private Boolean enabled; + @ApiModelProperty(value = "是否为admin账号", hidden = true) + @TableField(value = "is_admin") + private Boolean isAdmin = false; + @ApiModelProperty(value = "最后修改密码的时间", hidden = true) + @TableField(value = "pwd_reset_time") + 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); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemDeptExportRowVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemDeptExportRowVo.java new file mode 100644 index 00000000..ee234576 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemDeptExportRowVo.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2026 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.export; + +import cn.idev.excel.annotation.ExcelProperty; +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; +import java.util.Date; + +@Getter +@Setter +public class SystemDeptExportRowVo extends KitObject { + + @ExcelProperty("部门名称") + private String name; + @ExcelProperty("部门状态") + private String enabled; + @ExcelProperty("创建日期") + private Date createTime; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemDictExportRowVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemDictExportRowVo.java new file mode 100644 index 00000000..0df68de8 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemDictExportRowVo.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2026 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.export; + +import cn.idev.excel.annotation.ExcelProperty; +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; +import java.util.Date; + +@Getter +@Setter +public class SystemDictExportRowVo extends KitObject { + + @ExcelProperty("字典名称") + private String name; + @ExcelProperty("字典描述") + private String description; + @ExcelProperty("字典标签") + private String label; + @ExcelProperty("字典值") + private String value; + @ExcelProperty("创建日期") + private Date createTime; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemJobExportRowVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemJobExportRowVo.java new file mode 100644 index 00000000..c54d90ce --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemJobExportRowVo.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2026 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.export; + +import cn.idev.excel.annotation.ExcelProperty; +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; +import java.util.Date; + +@Getter +@Setter +public class SystemJobExportRowVo extends KitObject { + + @ExcelProperty("岗位名称") + private String name; + @ExcelProperty("岗位状态") + private String enabled; + @ExcelProperty("创建日期") + private Date createTime; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemLocalStorageExportRowVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemLocalStorageExportRowVo.java new file mode 100644 index 00000000..7bec3a35 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemLocalStorageExportRowVo.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021-2026 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.export; + +import cn.idev.excel.annotation.ExcelProperty; +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; +import java.util.Date; + +@Getter +@Setter +public class SystemLocalStorageExportRowVo extends KitObject { + + @ExcelProperty(value = "文件名") + private String realName; + @ExcelProperty(value = "备注名") + private String name; + @ExcelProperty(value = "文件类型") + private String type; + @ExcelProperty(value = "文件大小") + private String size; + @ExcelProperty(value = "创建者") + private String createBy; + @ExcelProperty(value = "创建日期") + private Date createTime; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemMenuExportRowVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemMenuExportRowVo.java new file mode 100644 index 00000000..bb32d88c --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemMenuExportRowVo.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2026 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.export; + +import cn.idev.excel.annotation.ExcelProperty; +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; +import java.util.Date; + +@Getter +@Setter +public class SystemMenuExportRowVo extends KitObject { + + @ExcelProperty("菜单标题") + private String title; + @ExcelProperty("菜单类型") + private String type; + @ExcelProperty("权限标识") + private String permission; + @ExcelProperty("外链菜单") + private String iFrame; + @ExcelProperty("菜单可见") + private String hidden; + @ExcelProperty("是否缓存") + private String cache; + @ExcelProperty("创建日期") + private Date createTime; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemOssStorageExportRowVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemOssStorageExportRowVo.java new file mode 100644 index 00000000..86da25e1 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemOssStorageExportRowVo.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2026 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.export; + +import cn.idev.excel.annotation.ExcelProperty; +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; +import java.util.Date; + +@Getter +@Setter +public class SystemOssStorageExportRowVo extends KitObject { + + @ExcelProperty("服务类型") + private String serviceType; + @ExcelProperty("服务地址") + private String endpoint; + @ExcelProperty("存储桶名称") + private String bucketName; + @ExcelProperty("文件名称") + private String fileName; + @ExcelProperty("文件大小") + private String fileSizeDesc; + @ExcelProperty("文件类型") + private String fileMime; + @ExcelProperty("创建人") + private String createBy; + @ExcelProperty("创建日期") + private Date createTime; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemQuartzJobExportRowVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemQuartzJobExportRowVo.java new file mode 100644 index 00000000..eb3dd3b4 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemQuartzJobExportRowVo.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2026 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.export; + +import cn.idev.excel.annotation.ExcelProperty; +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; +import java.util.Date; + +@Getter +@Setter +public class SystemQuartzJobExportRowVo extends KitObject { + + @ExcelProperty("任务名称") + private String jobName; + @ExcelProperty("Bean名称") + private String beanName; + @ExcelProperty("执行方法") + private String methodName; + @ExcelProperty("参数") + private String params; + @ExcelProperty("表达式") + private String cronExpression; + @ExcelProperty("状态") + private String isPause; + @ExcelProperty("描述") + private String description; + @ExcelProperty("创建日期") + private Date createTime; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemQuartzLogExportRowVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemQuartzLogExportRowVo.java new file mode 100644 index 00000000..c1d546c1 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemQuartzLogExportRowVo.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2026 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.export; + +import cn.idev.excel.annotation.ExcelProperty; +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; +import java.util.Date; + +@Getter +@Setter +public class SystemQuartzLogExportRowVo extends KitObject { + + @ExcelProperty("任务名称") + private String jobName; + @ExcelProperty("Bean名称") + private String beanName; + @ExcelProperty("执行方法") + private String methodName; + @ExcelProperty("参数") + private String params; + @ExcelProperty("表达式") + private String cronExpression; + @ExcelProperty("异常详情") + private String exceptionDetail; + @ExcelProperty("耗时/毫秒") + private Long time; + @ExcelProperty("状态") + private String status; + @ExcelProperty("创建日期") + private Date createTime; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemRoleExportRowVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemRoleExportRowVo.java new file mode 100644 index 00000000..cf89fb2e --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemRoleExportRowVo.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2026 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.export; + +import cn.idev.excel.annotation.ExcelProperty; +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; +import java.util.Date; + +@Getter +@Setter +public class SystemRoleExportRowVo extends KitObject { + + @ExcelProperty("角色名称") + private String name; + @ExcelProperty("角色级别") + private Integer level; + @ExcelProperty("描述") + private String description; + @ExcelProperty("角色名称") + private Date createTime; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemUserExportRowVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemUserExportRowVo.java new file mode 100644 index 00000000..c8f4a375 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemUserExportRowVo.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2026 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.export; + +import cn.idev.excel.annotation.ExcelProperty; +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; +import java.util.Date; + +@Getter +@Setter +public class SystemUserExportRowVo extends KitObject { + + @ExcelProperty("用户名") + private String username; + @ExcelProperty("角色") + private String roles; + @ExcelProperty("部门") + private String dept; + @ExcelProperty("岗位") + private String jobs; + @ExcelProperty("邮箱") + private String email; + @ExcelProperty("状态") + private String status; + @ExcelProperty("手机号码") + private String mobile; + @ExcelProperty("修改密码的时间") + private Date updatePwdTime; + @ExcelProperty("创建日期") + private Date createTime; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemUserOnlineExportRowVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemUserOnlineExportRowVo.java new file mode 100644 index 00000000..330524f7 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/export/SystemUserOnlineExportRowVo.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2026 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.export; + +import cn.idev.excel.annotation.ExcelProperty; +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SystemUserOnlineExportRowVo extends KitObject { + + @ExcelProperty("用户名") + private String userName; + @ExcelProperty("部门") + private String dept; + @ExcelProperty("登录IP") + private String ip; + @ExcelProperty("登录地点") + private String address; + @ExcelProperty("浏览器") + private String browser; + @ExcelProperty("登录日期") + private String loginTime; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCheckEmailCaptchaArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCheckEmailCaptchaArgs.java new file mode 100644 index 00000000..47bf284b --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCheckEmailCaptchaArgs.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2026 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.request; + +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; +import jakarta.validation.constraints.NotBlank; + +@Getter +@Setter +public class SystemCheckEmailCaptchaArgs extends KitObject { + + @NotBlank(message = "邮箱不能为空") + private String email; + @NotBlank(message = "验证码不能为空") + private String code; + @NotBlank(message = "验证码业务标识不能为空") + private String bizCode; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateDeptArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateDeptArgs.java new file mode 100644 index 00000000..56c481b5 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateDeptArgs.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2026 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.request; + +import cn.odboy.base.KitObject; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemCreateDeptArgs extends KitObject { + + @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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateDictArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateDictArgs.java new file mode 100644 index 00000000..b2ea87f6 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateDictArgs.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2026 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.request; + +import cn.odboy.base.KitObject; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.NotBlank; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemCreateDictArgs extends KitObject { + + @NotBlank(message = "字典名称必填") + private String name; + private String description; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateDictDetailArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateDictDetailArgs.java new file mode 100644 index 00000000..008a7565 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateDictDetailArgs.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2026 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.request; + +import cn.odboy.base.KitObject; +import cn.odboy.system.dal.dataobject.SystemDictTb; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemCreateDictDetailArgs extends KitObject { + + @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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateJobArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateJobArgs.java new file mode 100644 index 00000000..82007e9c --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateJobArgs.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2026 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.request; + +import cn.odboy.base.KitObject; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemCreateJobArgs extends KitObject { + + @NotBlank(message = "职位名称必填") + private String name; + @NotNull(message = "职位排序必填") + private Integer jobSort; + private Boolean enabled; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateRoleArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateRoleArgs.java new file mode 100644 index 00000000..c60d9143 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemCreateRoleArgs.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2026 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.request; + +import cn.odboy.base.KitObject; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.Set; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemCreateRoleArgs extends KitObject { + + @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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryDeptArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryDeptArgs.java new file mode 100644 index 00000000..9669b314 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryDeptArgs.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2026 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.request; + +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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryDictArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryDictArgs.java new file mode 100644 index 00000000..fc938a2a --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryDictArgs.java @@ -0,0 +1,26 @@ +/* + * Copyright 2021-2026 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.request; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class SystemQueryDictArgs { + + @ApiModelProperty(value = "模糊查询") + private String blurry; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryDictDetailArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryDictDetailArgs.java new file mode 100644 index 00000000..83d726b9 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryDictDetailArgs.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2026 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.request; + +import cn.odboy.base.KitObject; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SystemQueryDictDetailArgs extends KitObject { + + @ApiModelProperty(value = "标签") + private String label; + @ApiModelProperty(value = "字典名称") + private String dictName; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryJobArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryJobArgs.java new file mode 100644 index 00000000..fe7f27d6 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryJobArgs.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2026 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.request; + +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; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryMenuArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryMenuArgs.java new file mode 100644 index 00000000..d5a3de73 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryMenuArgs.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2026 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.request; + +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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryOperationLogArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryOperationLogArgs.java new file mode 100644 index 00000000..8825d4eb --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryOperationLogArgs.java @@ -0,0 +1,26 @@ +/* + * Copyright 2021-2026 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.request; + +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SystemQueryOperationLogArgs extends KitObject { + +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryQuartzJobArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryQuartzJobArgs.java new file mode 100644 index 00000000..45e63cb8 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryQuartzJobArgs.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021-2026 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.request; + +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; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryRoleArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryRoleArgs.java new file mode 100644 index 00000000..b424c5be --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryRoleArgs.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2026 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.request; + +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; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryStorageArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryStorageArgs.java new file mode 100644 index 00000000..78d69aa2 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryStorageArgs.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2026 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.request; + +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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryUserArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryUserArgs.java new file mode 100644 index 00000000..6af6f08e --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemQueryUserArgs.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2026 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.request; + +import cn.odboy.base.KitObject; +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 KitObject { + + @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; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemSendEmailArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemSendEmailArgs.java new file mode 100644 index 00000000..33e2580d --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemSendEmailArgs.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2026 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.request; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import jakarta.validation.constraints.NotBlank; +import jakarta.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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemUpdateQuartzJobArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemUpdateQuartzJobArgs.java new file mode 100644 index 00000000..3dd546a1 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemUpdateQuartzJobArgs.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2026 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.request; + +import cn.odboy.base.KitObject; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemUpdateQuartzJobArgs extends KitObject { + + @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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemUpdateUserPasswordArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemUpdateUserPasswordArgs.java new file mode 100644 index 00000000..c33aa8c3 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemUpdateUserPasswordArgs.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2026 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.request; + +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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemUserLoginArgs.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemUserLoginArgs.java new file mode 100644 index 00000000..636b9dd3 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/request/SystemUserLoginArgs.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2026 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.request; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import jakarta.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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemAuthVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemAuthVo.java new file mode 100644 index 00000000..24f6cb3a --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemAuthVo.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2026 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.response; + +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; + +/** + * 登录成功后:token 与 用户信息 + */ +@Getter +@Setter +public class SystemAuthVo extends KitObject { + + private String token; + private SystemUserInfoVo user; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemCaptchaVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemCaptchaVo.java new file mode 100644 index 00000000..6bc528e8 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemCaptchaVo.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2026 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.response; + +import cn.odboy.base.KitObject; +import lombok.Getter; +import lombok.Setter; + +/** + * 验证码信息 + */ +@Getter +@Setter +public class SystemCaptchaVo extends KitObject { + + /** + * redis key + */ + private String uuid; + /** + * image base64 + */ + private String img; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemDeptVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemDeptVo.java new file mode 100644 index 00000000..1f1d19b5 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemDeptVo.java @@ -0,0 +1,83 @@ +/* + * Copyright 2021-2026 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.response; + +import cn.odboy.base.KitBaseUserTimeTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +@Getter +@Setter +public class SystemDeptVo extends KitBaseUserTimeTb { + + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + @ApiModelProperty(value = "排序") + private Integer deptSort; + @ApiModelProperty(value = "部门名称") + private String name; + @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; + } + SystemDeptVo dept = (SystemDeptVo) 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; + } + + /** + * 扩展字段 + */ + @ApiModelProperty(value = "角色") + private Set roles; + @ApiModelProperty(value = "子部门") + private List children; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemDictDetailVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemDictDetailVo.java new file mode 100644 index 00000000..fa6e56a4 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemDictDetailVo.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2026 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.response; + +import cn.odboy.base.KitBaseUserTimeTb; +import cn.odboy.system.dal.dataobject.SystemDictTb; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SystemDictDetailVo extends KitBaseUserTimeTb { + + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + @ApiModelProperty(hidden = true) + private Long dictId; + @ApiModelProperty(value = "字典标签") + private String label; + @ApiModelProperty(value = "字典值") + private String value; + @ApiModelProperty(value = "排序") + private Integer dictSort = 999; + /** + * 扩展字段 + */ + @ApiModelProperty(value = "字典") + private SystemDictTb dict; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemMenuMetaVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemMenuMetaVo.java new file mode 100644 index 00000000..64078814 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemMenuMetaVo.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2026 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.response; + +import cn.odboy.base.KitObject; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class SystemMenuMetaVo extends KitObject { + + @ApiModelProperty(value = "菜单标题") + private String title; + @ApiModelProperty(value = "菜单图标") + private String icon; + @ApiModelProperty(value = "缓存") + private Boolean noCache; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemMenuRouterVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemMenuRouterVo.java new file mode 100644 index 00000000..61d9a9ea --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemMenuRouterVo.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2026 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.response; + +import cn.odboy.base.KitObject; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.List; + +/** + * 构建前端路由时用到 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemMenuRouterVo extends KitObject { + + @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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemMenuVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemMenuVo.java new file mode 100644 index 00000000..b967e836 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemMenuVo.java @@ -0,0 +1,99 @@ +/* + * Copyright 2021-2026 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.response; + +import cn.odboy.base.KitBaseUserTimeTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +@Getter +@Setter +public class SystemMenuVo extends KitBaseUserTimeTb { + + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + @ApiModelProperty(value = "菜单标题") + private String title; + @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; + } + SystemMenuVo menu = (SystemMenuVo) 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; + } + + /** + * 附加属性 + */ + @ApiModelProperty(value = "菜单角色") + private Set roles; + @ApiModelProperty(value = "子菜单") + private List children; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemOssStorageVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemOssStorageVo.java new file mode 100644 index 00000000..910304c4 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemOssStorageVo.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2026 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.response; + +import cn.odboy.base.KitBaseUserTimeTb; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SystemOssStorageVo extends KitBaseUserTimeTb { + + @ApiModelProperty("ID") + private Long id; + @ApiModelProperty("类型,比如minio") + private String serviceType; + @ApiModelProperty("服务地址") + private String endpoint; + @ApiModelProperty("存储桶名称") + private String bucketName; + @ApiModelProperty("完整文件名称") + private String fileName; + @ApiModelProperty("文件大小, 单位:字节") + private Long fileSize; + @ApiModelProperty("文件类型") + private String fileMime; + @ApiModelProperty("短文件名") + private String filePrefix; + @ApiModelProperty("文件后缀") + private String fileSuffix; + @ApiModelProperty("文件md5") + private String fileMd5; + @ApiModelProperty("文件链接") + private String fileUrl; + @ApiModelProperty("文件编码") + private String fileCode; + @ApiModelProperty("对象路径") + private String objectName; + /** + * 扩展字段 + */ + private String fileSizeDesc; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemRoleCodeVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemRoleCodeVo.java new file mode 100644 index 00000000..52710c8e --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemRoleCodeVo.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2026 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.response; + +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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemRoleVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemRoleVo.java new file mode 100644 index 00000000..9c57efe8 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemRoleVo.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021-2026 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.response; + +import cn.odboy.base.KitBaseUserTimeTb; +import cn.odboy.system.constant.SystemDataScopeEnum; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.dataobject.SystemMenuTb; +import cn.odboy.system.dal.dataobject.SystemUserTb; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import java.util.Objects; +import java.util.Set; + +@Getter +@Setter +public class SystemRoleVo extends KitBaseUserTimeTb { + + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + @ApiModelProperty(value = "名称", hidden = true) + private String name; + @ApiModelProperty(value = "数据权限,全部 、 本级 、 自定义") + private String dataScope = SystemDataScopeEnum.THIS_LEVEL.getValue(); + @ApiModelProperty(value = "级别,数值越小,级别越大") + private Integer level = 3; + @ApiModelProperty(value = "描述") + private String description; + /** + * 扩展字段 + */ + @ApiModelProperty(value = "用户", hidden = true) + private Set users; + @ApiModelProperty(value = "菜单", hidden = true) + private Set menus; + @ApiModelProperty(value = "部门", hidden = true) + private Set depts; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SystemRoleVo role = (SystemRoleVo) o; + return Objects.equals(id, role.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemSimpleUserVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemSimpleUserVo.java new file mode 100644 index 00000000..75862f38 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemSimpleUserVo.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021-2026 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.response; + +import cn.odboy.base.KitObject; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.dataobject.SystemJobTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Set; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SystemSimpleUserVo extends KitObject { + + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + @ApiModelProperty(value = "用户角色") + private Set roles; + @ApiModelProperty(value = "用户岗位") + private Set jobs; + private Long deptId; + 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; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserInfoVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserInfoVo.java new file mode 100644 index 00000000..9a203890 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserInfoVo.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2026 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.response; + +import cn.odboy.base.KitObject; +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 KitObject { + + 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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserJwtVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserJwtVo.java new file mode 100644 index 00000000..9d406451 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserJwtVo.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2026 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.response; + +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 SystemUserVo 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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserOnlineVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserOnlineVo.java new file mode 100644 index 00000000..b4faeaa4 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserOnlineVo.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2026 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.response; + +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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserVo.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserVo.java new file mode 100644 index 00000000..cb947f10 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/model/response/SystemUserVo.java @@ -0,0 +1,87 @@ +/* + * Copyright 2021-2026 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.response; + +import cn.odboy.base.KitBaseUserTimeTb; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.dataobject.SystemJobTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; +import java.util.Date; +import java.util.Objects; +import java.util.Set; + +@Getter +@Setter +public class SystemUserVo extends KitBaseUserTimeTb { + + @ApiModelProperty(value = "ID", hidden = true) + private Long id; + @ApiModelProperty(hidden = true) + private Long deptId; + @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 = "头像存储的路径", hidden = true) + private String avatarPath; + @ApiModelProperty(value = "密码") + private String password; + @ApiModelProperty(value = "是否启用") + private Boolean enabled; + @ApiModelProperty(value = "是否为admin账号", hidden = true) + private Boolean isAdmin = false; + @ApiModelProperty(value = "最后修改密码的时间", hidden = true) + private Date pwdResetTime; + + /** + * 扩展字段 + */ + @ApiModelProperty(value = "用户部门") + private SystemDeptTb dept; + @ApiModelProperty(value = "用户角色") + private Set roles; + @ApiModelProperty(value = "用户岗位") + private Set jobs; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SystemUserVo user = (SystemUserVo) o; + return Objects.equals(id, user.id) && Objects.equals(username, user.username); + } + + @Override + public int hashCode() { + return Objects.hash(id, username); + } + +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDeptMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDeptMapper.java new file mode 100644 index 00000000..a95b7246 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDeptMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2026 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 com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 部门 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemDeptMapper extends BaseMapper { + + default void updateDeptSubCountById(long count, Long deptId) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(SystemDeptTb::getId, deptId); + wrapper.set(SystemDeptTb::getSubCount, count); + update(null, wrapper); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictDetailMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictDetailMapper.java new file mode 100644 index 00000000..424eecd7 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictDetailMapper.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2026 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.request.SystemQueryDictDetailArgs; +import cn.odboy.system.dal.model.response.SystemDictDetailVo; +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 SystemDictDetailMapper extends BaseMapper { + + IPage selectPageByArgs(Page page, @Param("args") SystemQueryDictDetailArgs args); + + List listDictDetailByName(@Param("name") String name); +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictMapper.java new file mode 100644 index 00000000..00409d3a --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemDictMapper.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2026 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 com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 字典 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemDictMapper extends BaseMapper { + + default SystemDictTb getByName(String dictName) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemDictTb::getName, dictName); + return selectOne(wrapper); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemEmailConfigMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemEmailConfigMapper.java new file mode 100644 index 00000000..c34b909b --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemEmailConfigMapper.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemJobMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemJobMapper.java new file mode 100644 index 00000000..779fad51 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemJobMapper.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2026 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 com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 职位 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemJobMapper extends BaseMapper { + +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemLocalStorageMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemLocalStorageMapper.java new file mode 100644 index 00000000..2f0355c4 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemLocalStorageMapper.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2026 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 com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 本地存储记录 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemLocalStorageMapper extends BaseMapper { + +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemMenuMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemMenuMapper.java new file mode 100644 index 00000000..b19e4afe --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemMenuMapper.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2026 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 com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import java.util.List; + +/** + * 菜单 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemMenuMapper extends BaseMapper { + + default List listAll() { + return selectList(null); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOperationLogMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOperationLogMapper.java new file mode 100644 index 00000000..c79cc894 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOperationLogMapper.java @@ -0,0 +1,25 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOssStorageMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOssStorageMapper.java new file mode 100644 index 00000000..24cac74e --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemOssStorageMapper.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2026 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 com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * OSS存储 Mapper 接口 + *

+ * + * @author codegen + * @since 2025-07-15 + */ +@Mapper +public interface SystemOssStorageMapper extends BaseMapper { + +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzJobMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzJobMapper.java new file mode 100644 index 00000000..1cc5a5b7 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzJobMapper.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2026 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 com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 定时任务 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemQuartzJobMapper extends BaseMapper { + +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzLogMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzLogMapper.java new file mode 100644 index 00000000..0913d583 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemQuartzLogMapper.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2026 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 com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 定时任务日志 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemQuartzLogMapper extends BaseMapper { + +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleDeptMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleDeptMapper.java new file mode 100644 index 00000000..ddec61d7 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleDeptMapper.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2026 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.dataobject.SystemRoleDeptTb; +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 SystemRoleDeptMapper extends BaseMapper { + + Set listUserDeptByRoleId(@Param("roleId") Long roleId); +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMapper.java new file mode 100644 index 00000000..8de3bf3c --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMapper.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2026 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 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 SystemRoleMapper extends BaseMapper { + + List listRoleLevelByRoleIds(@Param("roleIds") List roleIds); +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMenuMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMenuMapper.java new file mode 100644 index 00000000..544d56e3 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemRoleMenuMapper.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2026 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.List; +import java.util.Set; + +/** + * 角色菜单关联 Mapper + * + * @author odboy + */ +@Mapper +public interface SystemRoleMenuMapper extends BaseMapper { + + Set listMenuPermissionByRoleIds(@Param("roleIds") Set roleIds); + + List listMenuWithRoleIdsNeButton(@Param("roleIds") Set roleIds); + + List listMenuWithRoleIds(@Param("roleIds") Set roleIds); +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserJobMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserJobMapper.java new file mode 100644 index 00000000..415c15c9 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserJobMapper.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2026 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.dataobject.SystemUserJobTb; +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 SystemUserJobMapper extends BaseMapper { + + Set listUserJobByUserId(@Param("userId") Long userId); +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserMapper.java new file mode 100644 index 00000000..d23f4744 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserMapper.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2026 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 com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +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 SystemUserMapper extends BaseMapper { + + default void updateUserPasswordByUsername(String username, String password) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(SystemUserTb::getUsername, username); + wrapper.set(SystemUserTb::getPassword, password); + update(null, wrapper); + } + + default void updateUserEmailByUsername(String username, String email) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(SystemUserTb::getUsername, username); + wrapper.set(SystemUserTb::getEmail, email); + update(null, wrapper); + } + + default void updateUserPasswordByUserIds(String password, Set userIds) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.in(SystemUserTb::getId, userIds); + wrapper.set(SystemUserTb::getPassword, password); + update(null, wrapper); + } + + List listUsernameByIds(@Param("userIds") Set userIds); +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserRoleMapper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserRoleMapper.java new file mode 100644 index 00000000..698fb37f --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/mysql/SystemUserRoleMapper.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2026 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.dataobject.SystemUserRoleTb; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import java.util.Set; + +@Mapper +public interface SystemUserRoleMapper extends BaseMapper { + + Set listUserRoleByUserId(@Param("userId") Long userId); + + Set listUserRoleLevelByUserId(@Param("userId") Long userId); + + Set listUserRoleIdByUserId(@Param("userId") Long userId); +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserInfoDAO.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserInfoDAO.java new file mode 100644 index 00000000..f5ec2465 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserInfoDAO.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021-2026 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; + +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.framework.redis.KitRedisHelper; +import cn.odboy.system.constant.SystemCacheKey; +import cn.odboy.system.dal.model.response.SystemUserJwtVo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * 用户缓存管理 + */ +@Component +public class SystemUserInfoDAO { + + @Autowired + private KitRedisHelper 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(SystemCacheKey.USER_INFO + userName, user, time); + } + } + + /** + * 清理用户缓存信息 用户信息变更时 + * + * @param userName 用户名 + */ + @Async + public void deleteUserLoginInfoByUserName(String userName) { + if (StrUtil.isNotEmpty(userName)) { + // 清除数据 + redisHelper.del(SystemCacheKey.USER_INFO + userName); + } + } + + /** + * 返回用户缓存 + * + * @param username 用户名 + * @return UserJwtVo + */ + public SystemUserJwtVo getUserLoginInfoByUserName(String username) { + if (StrUtil.isNotEmpty(username)) { + // 获取数据 + return redisHelper.get(SystemCacheKey.USER_INFO + username, SystemUserJwtVo.class); + } + return null; + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserOnlineInfoDAO.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserOnlineInfoDAO.java new file mode 100644 index 00000000..12a7f525 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/dal/redis/SystemUserOnlineInfoDAO.java @@ -0,0 +1,188 @@ +/* + * Copyright 2021-2026 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; + +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.KitPageResult; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.redis.KitRedisHelper; +import cn.odboy.system.constant.SystemCacheKey; +import cn.odboy.system.dal.model.export.SystemUserOnlineExportRowVo; +import cn.odboy.system.dal.model.response.SystemUserJwtVo; +import cn.odboy.system.dal.model.response.SystemUserOnlineVo; +import cn.odboy.system.framework.permission.core.handler.TokenProvider; +import cn.odboy.util.KitBeanUtil; +import cn.odboy.util.KitBrowserUtil; +import cn.odboy.util.KitDesEncryptUtil; +import cn.odboy.util.KitIPUtil; +import cn.odboy.util.KitPageUtil; +import cn.odboy.util.xlsx.KitExcelExporter; +import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +@AllArgsConstructor +public class SystemUserOnlineInfoDAO { + + private final AppProperties properties; + private final TokenProvider tokenProvider; + private final KitRedisHelper redisHelper; + + /** + * 保存在线用户信息 + * + * @param userJwtVo / + * @param token / + * @param request / + */ + public void saveUserJwtModelByToken(SystemUserJwtVo userJwtVo, String token, HttpServletRequest request) { + String dept = userJwtVo.getUser().getDept().getName(); + String ip = KitBrowserUtil.getIp(request); + String id = tokenProvider.getId(token); + String version = KitBrowserUtil.getVersion(request); + String address = KitIPUtil.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(KitDesEncryptUtil.desEncrypt(token)); + userOnlineVo.setLoginTime(new Date()); + } catch (Exception e) { + log.error("保存在线用户信息失败", 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); +// } +// KitFileUtil.downloadExcel(list, response); +// } + + /** + * 根据用户名强退用户 + * + * @param username / + */ + public void kickOutByUsername(String username) { + String loginKey = SystemCacheKey.ONLINE_USER + username + "*"; + redisHelper.scanDel(loginKey); + } + + /** + * 查询全部数据 + * + * @param onlineVo / + * @param pageable / + * @return / + */ + public KitPageResult searchOnlineUser(SystemUserOnlineVo onlineVo, IPage pageable) { + String username = null; + if (onlineVo != null) { + username = onlineVo.getUserName(); + } + List onlineUserList = this.queryUserOnlineModelListByUsername(username); + List paging = KitPageUtil.softPaging(pageable.getCurrent(), pageable.getSize(), onlineUserList); + return KitPageUtil.toPage(paging, onlineUserList.size()); + } + + /** + * 查询全部数据,不分页 + * + * @param username / + * @return / + */ + public List queryUserOnlineModelListByUsername(String username) { + String loginKey = SystemCacheKey.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); + } + + public void kickOutUser(Set keys) throws Exception { + for (String token : keys) { + // 解密Key + token = KitDesEncryptUtil.desDecrypt(token); + this.logoutByToken(token); + } + } + + public void exportOnlineUserXlsx(HttpServletResponse response, String username) { + List userOnlineVos = this.queryUserOnlineModelListByUsername(username); + List rowVos = KitBeanUtil.copyToList(userOnlineVos, SystemUserOnlineExportRowVo.class); + KitExcelExporter.exportSimple(response, "在线用户数据", SystemUserOnlineExportRowVo.class, rowVos); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java new file mode 100644 index 00000000..d986fe8e --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java @@ -0,0 +1,98 @@ +/* + * Copyright 2021-2026 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.features; + +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.KitPageArgs; +import cn.odboy.feature.KitDynamicTableResponse; +import cn.odboy.feature.KitDynamicTableUtil; +import cn.odboy.system.dal.dataobject.SystemMenuTb; +import cn.odboy.system.dal.model.request.SystemQueryMenuArgs; +import cn.odboy.system.dal.mysql.SystemMenuMapper; +import cn.odboy.system.features.model.SystemMenuDynamicTableModel; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Api(tags = "系统组件:CuteDynamicTable演示") +@RequestMapping("/api/features/CuteDynamicTable") +public class CuteDynamicTableDemoApi { + + @Autowired + private SystemMenuMapper systemMenuMapper; + + @ApiOperation("写法1:查询动态数据") + @PostMapping(value = "/searchMenu") + @PreAuthorize("@el.check()") + public ResponseEntity> searchMenu(@Validated @RequestBody KitPageArgs pageArgs) { + // 请求参数 + Integer page = pageArgs.getPage(); + Integer size = pageArgs.getSize(); + KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy(); + SystemQueryMenuArgs args = pageArgs.getArgs(); + // 分页查询 + QueryWrapper wrapper = new QueryWrapper<>(); + if (orderBy != null) { + orderBy.bindWrapper(wrapper); + } + IPage iPage = new Page<>(page, size); + LambdaQueryWrapper lambda = wrapper.lambda(); + if (args != null) { + lambda.like(StrUtil.isNotBlank(args.getBlurry()), SystemMenuTb::getTitle, args.getBlurry()); + } + IPage pageResult = systemMenuMapper.selectPage(iPage, wrapper); + // 响应结果 + return ResponseEntity.ok(new KitDynamicTableResponse<>(SystemMenuDynamicTableModel.class, pageResult, "id")); + } + + @ApiOperation("写法2:查询动态数据") + @PostMapping(value = "/searchMenu2") + @PreAuthorize("@el.check()") + public ResponseEntity> searchMenu2(@Validated @RequestBody KitPageArgs pageArgs) { + // 渲染排序(非必须) + QueryWrapper wrapper = KitDynamicTableUtil.renderOrder(pageArgs); + LambdaQueryWrapper lambda = wrapper.lambda(); + // 查询条件 + SystemQueryMenuArgs args = pageArgs.getArgs(); + if (args != null) { + lambda.like(StrUtil.isNotBlank(args.getBlurry()), SystemMenuTb::getTitle, args.getBlurry()); + } + // 响应结果 + return ResponseEntity.ok(new KitDynamicTableResponse<>(SystemMenuDynamicTableModel.class, pageArgs, "id", systemMenuMapper, lambda)); + } + + @ApiOperation("写法3:查询动态数据") + @PostMapping(value = "/searchMenu3") + @PreAuthorize("@el.check()") + public ResponseEntity> searchMenu3(@Validated @RequestBody KitPageArgs pageArgs) { + // 渲染查询条件 + LambdaQueryWrapper wrapper = KitDynamicTableUtil.renderAll(pageArgs); + // 响应结果 + return ResponseEntity.ok(new KitDynamicTableResponse<>(SystemMenuDynamicTableModel.class, pageArgs, "id", systemMenuMapper, wrapper)); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/features/model/SystemMenuDynamicTableModel.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/features/model/SystemMenuDynamicTableModel.java new file mode 100644 index 00000000..26852dd1 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/features/model/SystemMenuDynamicTableModel.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2026 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.features.model; + +import cn.odboy.base.KitObject; +import cn.odboy.feature.KitDynamicTableColumn; +import lombok.Getter; +import lombok.Setter; + +/** + * 例子:渲染对象 + */ +@Getter +@Setter +public class SystemMenuDynamicTableModel extends KitObject { + + @KitDynamicTableColumn(value = "ID") + private Long id; + @KitDynamicTableColumn(value = "菜单标题") + private String title; + @KitDynamicTableColumn(value = "菜单组件名称", width = 200) + private String componentName; + @KitDynamicTableColumn(value = "排序") + private Integer menuSort = 999; + @KitDynamicTableColumn(value = "组件路径", width = 200) + private String component; + @KitDynamicTableColumn(value = "路由地址", width = 120) + private String path; + @KitDynamicTableColumn(value = "菜单类型") + private Integer type; + @KitDynamicTableColumn(value = "权限标识") + private String permission; + @KitDynamicTableColumn(value = "菜单图标") + private String icon; + @KitDynamicTableColumn(value = "缓存") + private Boolean cache; + @KitDynamicTableColumn(value = "是否隐藏") + private Boolean hidden; + @KitDynamicTableColumn(value = "上级菜单") + private Long pid; + @KitDynamicTableColumn(value = "子节点数目", sortable = true, width = 200) + private Integer subCount = 0; + @KitDynamicTableColumn(value = "外链菜单") + private Boolean iFrame; +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLock.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLock.java new file mode 100644 index 00000000..78624073 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLock.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021-2026 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLockAspect.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLockAspect.java new file mode 100644 index 00000000..51639674 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/bizlock/BizLockAspect.java @@ -0,0 +1,87 @@ +/* + * Copyright 2021-2026 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.KitRedisHelper; +import java.lang.reflect.Method; +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; + +@Aspect +@Component +public class BizLockAspect { + + private final KitRedisHelper redisHelper; + private final ExpressionParser parser = new SpelExpressionParser(); + + public BizLockAspect(KitRedisHelper redisHelper) { + this.redisHelper = redisHelper; + } + + @Around("@annotation(bizLock)") + public Object around(ProceedingJoinPoint joinPoint, BizLock bizLock) throws Throwable { + // 解析SpEL表达式生成最终的lockKey + String lockKey = this.parseLockKey(joinPoint, bizLock); + boolean success = redisHelper.setIfAbsent(lockKey, lockKey, bizLock.leaseTime(), bizLock.timeUnit()); + if (!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]); + // 支持#p0,#p1格式 + context.setVariable("p" + i, args[i]); + } + // 设置方法相关信息 + 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); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/mybatisplus/MpMetaObjectHandler.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/mybatisplus/MpMetaObjectHandler.java new file mode 100644 index 00000000..69311f7e --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/mybatisplus/MpMetaObjectHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2026 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.KitSecurityHelper; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import java.util.Date; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +@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 = KitSecurityHelper.safeGetUsername(); + 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 = KitSecurityHelper.safeGetUsername(); + this.strictUpdateFill(metaObject, "updateBy", String.class, username); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLog.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLog.java new file mode 100644 index 00000000..c9d09332 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLog.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2026 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 捕捉操作日志 + * + * @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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLogAspect.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLogAspect.java new file mode 100644 index 00000000..4732829c --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/operalog/OperationLogAspect.java @@ -0,0 +1,128 @@ +/* + * Copyright 2021-2026 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.KitRequestHolder; +import cn.odboy.system.dal.dataobject.SystemOperationLogTb; +import cn.odboy.system.dal.mysql.SystemOperationLogMapper; +import cn.odboy.system.framework.permission.core.KitSecurityHelper; +import cn.odboy.util.KitBrowserUtil; +import cn.odboy.util.KitIPUtil; +import com.alibaba.fastjson2.JSON; +import io.swagger.annotations.ApiOperation; +import jakarta.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; +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; + +/** + * 捕捉操作日志 + * + * @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 this.handleLog(joinPoint, operationLog); + } + + private Object handleLog(ProceedingJoinPoint joinPoint, OperationLog annotation) throws Throwable { + TimeInterval timeInterval = new TimeInterval(); + try { + Object result = joinPoint.proceed(); + long interval = timeInterval.interval(); + SystemOperationLogTb record = this.getOperationLogTb(joinPoint, annotation, interval); + ThreadUtil.execAsync( + () -> { + try { + systemOperationLogMapper.insert(record); + } catch (Exception e) { + // 忽略 + } + }); + return result; + } catch (Throwable exception) { + long interval = timeInterval.interval(); + SystemOperationLogTb record = this.getOperationLogTb(joinPoint, annotation, interval); + record.setExceptionDetail(ExceptionUtil.stacktraceToString(exception)); + ThreadUtil.execAsync( + () -> { + try { + systemOperationLogMapper.insert(record); + } catch (Exception e) { + // 忽略 + } + }); + throw exception; + } + } + + private SystemOperationLogTb getOperationLogTb( + ProceedingJoinPoint joinPoint, OperationLog annotation, long executeTime) { + 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 = ""; + try { + params = JSON.toJSONString(args); + } catch (Exception e) { + // 忽略异常 + } + HttpServletRequest request = KitRequestHolder.getHttpServletRequest(); + String requestIp = KitBrowserUtil.getIp(request); + String browserInfo = KitBrowserUtil.getVersion(request); + String address = KitIPUtil.getCityInfo(requestIp); + String username = KitSecurityHelper.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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/AuthorityConfig.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/AuthorityConfig.java new file mode 100644 index 00000000..62fdc2cc --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/AuthorityConfig.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2026 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.KitSecurityHelper; +import java.util.Arrays; +import java.util.List; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Service; + +@Service(value = "el") +public class AuthorityConfig { + + /** + * 判断接口是否有权限 + * + * @param permissions 权限 + * @return / + */ + public Boolean check(String... permissions) { + // 获取当前用户的所有权限 + List roleList = + KitSecurityHelper.getCurrentUser().getAuthorities().stream().map(GrantedAuthority::getAuthority).toList(); + // 判断当前用户的所有权限是否包含接口上定义的权限 + return roleList.contains("admin") || Arrays.stream(permissions).anyMatch(roleList::contains); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/SpringSecurityConfig.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/SpringSecurityConfig.java new file mode 100644 index 00000000..0d60de35 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/config/SpringSecurityConfig.java @@ -0,0 +1,125 @@ +/* + * Copyright 2021-2026 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.TokenFilter; +import cn.odboy.system.framework.permission.core.handler.TokenProvider; +import cn.odboy.util.KitAnonTagUtil; +import java.util.Map; +import java.util.Set; +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.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; +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.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.filter.CorsFilter; + +@Configuration +@EnableMethodSecurity(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 = KitAnonTagUtil.getAnonymousUrl(applicationContext); + + // 创建TokenFilter实例 + TokenFilter customFilter = new TokenFilter(tokenProvider, systemUserOnlineInfoDAO); + + return httpSecurity + // 禁用 CSRF + .csrf(AbstractHttpConfigurer::disable).addFilter(corsFilter) + .addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class) + // 授权异常 + .exceptionHandling( + exceptionHandling -> exceptionHandling + .authenticationEntryPoint(authenticationErrorHandler) + .accessDeniedHandler(jwtAccessDeniedHandler) + ) + // 防止iframe 造成跨域 + .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) + // 不创建会话 + .sessionManagement( + sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(authorizeRequests -> authorizeRequests + // 静态资源等 + .requestMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/websocket/**").permitAll() + // Knife4j 和 OpenAPI 相关路径 + .requestMatchers("/doc.html", "/webjars/**", "/v3/api-docs", "/v3/api-docs/**", "/swagger-resources", + "/swagger-resources/**", "/configuration/ui", "/configuration/security").permitAll() + // 文件 + .requestMatchers("/avatar/**").permitAll().requestMatchers("/file/**").permitAll() + // 阿里巴巴 druid + .requestMatchers("/META-INF/druid/**").permitAll() + // 放行OPTIONS请求 + .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() + // 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型 + // GET + .requestMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll() + // POST + .requestMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll() + // PUT + .requestMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll() + // PATCH + .requestMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll() + // DELETE + .requestMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll() + // 所有类型的接口都放行 + .requestMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll() + // 所有请求都需要认证 + .anyRequest().authenticated()).build(); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/KitSecurityHelper.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/KitSecurityHelper.java new file mode 100644 index 00000000..b20766af --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/KitSecurityHelper.java @@ -0,0 +1,151 @@ +/* + * Copyright 2021-2026 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.KitSpringBeanHolder; +import cn.odboy.system.constant.SystemDataScopeEnum; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import jakarta.servlet.http.HttpServletRequest; + +import java.util.List; +import java.util.Objects; + +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; + +/** + * 获取当前登录的用户 + */ +@Slf4j +@Component +public class KitSecurityHelper { + + /** + * 获取当前登录的用户 + * + * @return UserDetails + */ + public static UserDetails getCurrentUser() { + UserDetailsService userDetailsService = KitSpringBeanHolder.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 safeGetUsername() { + try { + return getCurrentUsername(getToken()); + } catch (Exception e) { + return "anonymous"; + } + } + + /** + * 获取系统用户名称 + * + * @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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAccessDeniedHandler.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAccessDeniedHandler.java new file mode 100644 index 00000000..57e76267 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAccessDeniedHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2026 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 jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +@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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAuthenticationEntryPoint.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAuthenticationEntryPoint.java new file mode 100644 index 00000000..bc0594ce --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/JwtAuthenticationEntryPoint.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2026 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 jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +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; + +@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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenFilter.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenFilter.java new file mode 100644 index 00000000..2cfdffb4 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenFilter.java @@ -0,0 +1,88 @@ +/* + * Copyright 2021-2026 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.response.SystemUserOnlineVo; +import cn.odboy.system.dal.redis.SystemUserOnlineInfoDAO; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; +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; + +@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 = this.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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenProvider.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenProvider.java new file mode 100644 index 00000000..042169d4 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/TokenProvider.java @@ -0,0 +1,167 @@ +/* + * Copyright 2021-2026 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.KitRedisHelper; +import cn.odboy.system.dal.model.response.SystemUserJwtVo; +import cn.odboy.system.constant.SystemCacheKey; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtParser; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.crypto.SecretKey; +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; + +@Slf4j +@Component +public class TokenProvider implements InitializingBean { + + public static final String AUTHORITIES_UUID_KEY = "uid"; + public static final String AUTHORITIES_UID_KEY = "userId"; + @Autowired + private KitRedisHelper redisHelper; + @Autowired + private AppProperties properties; + // 0.11.x版本 + // private Key signingKey; + // 0.12.x版本 + private SecretKey signingKey; + private JwtParser jwtParser; + + @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 / + */ + protected Authentication getAuthentication(String token) { + Claims claims = this.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 = this.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 = this.getClaims(token); + return SystemCacheKey.ONLINE_USER + claims.getSubject() + ":" + getId(token); + } + + /** + * 获取会话编号 + * + * @param token / + * @return / + */ + public String getId(String token) { + Claims claims = this.getClaims(token); + return claims.get(AUTHORITIES_UUID_KEY, String.class); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/UserDetailsHandler.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/UserDetailsHandler.java new file mode 100644 index 00000000..2ef1b543 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/permission/core/handler/UserDetailsHandler.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2026 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.model.response.SystemRoleCodeVo; +import cn.odboy.system.dal.model.response.SystemUserJwtVo; +import cn.odboy.system.dal.model.response.SystemUserVo; +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; + +/** + * 通过用户名判断用户信息,并获取拥有的权限与部门 + */ +@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) { + SystemUserVo user = systemUserService.getUserVoWithPasswordByUsername(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.queryDeptIdByArgs(user), authorities); + // 添加缓存数据 + systemUserInfoDAO.saveUserLoginInfoByUserName(username, userJwtVo); + } + } + return userJwtVo; + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ActiveJobRegister.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ActiveJobRegister.java new file mode 100644 index 00000000..85f48264 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ActiveJobRegister.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021-2026 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.service.SystemQuartzJobService; +import jakarta.annotation.Nonnull; +import java.util.List; +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; + +/** + * 应用初始化完毕后,注册定时任务 + * + * @author odboy + */ +@Slf4j +@Component +public class ActiveJobRegister implements ApplicationRunner { + + @Autowired + private SystemQuartzJobService systemQuartzJobService; + @Autowired + private QuartzManage quartzManage; + + /** + * 项目启动时重新激活启用的定时任务 + * + * @param applicationArguments / + */ + @Override + public void run(@Nonnull ApplicationArguments applicationArguments) { + List quartzJobTbs = systemQuartzJobService.listEnableQuartzJob(); + quartzJobTbs.forEach(quartzManage::addJob); + log.info("Timing task injection complete"); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ExecutionJobBean.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ExecutionJobBean.java new file mode 100644 index 00000000..0010361f --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/ExecutionJobBean.java @@ -0,0 +1,137 @@ +/* + * Copyright 2021-2026 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.KitSpringBeanHolder; +import cn.odboy.framework.quartz.core.QuartzRunnable; +import cn.odboy.framework.redis.KitRedisHelper; +import cn.odboy.system.dal.dataobject.SystemQuartzJobTb; +import cn.odboy.system.dal.dataobject.SystemQuartzLogTb; +import cn.odboy.system.dal.model.request.SystemSendEmailArgs; +import cn.odboy.system.dal.mysql.SystemQuartzLogMapper; +import cn.odboy.system.service.SystemEmailService; +import cn.odboy.system.service.SystemQuartzJobService; +import cn.odboy.util.KitBeanUtil; +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; + +/** + * 任务定义,继承QuartzJobBean是固定写法 + * + * @author odboy + */ +@Slf4j +public class ExecutionJobBean extends QuartzJobBean { + + /** + * 此处仅供参考,可根据任务执行情况自定义线程池参数 + */ + private final ThreadPoolTaskExecutor executor = KitSpringBeanHolder.getBean("taskAsync"); + + @Override + public void executeInternal(JobExecutionContext context) { + // 获取任务 + SystemQuartzJobTb quartzJob = (SystemQuartzJobTb) context.getMergedJobDataMap().get(SystemQuartzJobTb.JOB_KEY); + // 获取spring bean + SystemQuartzLogMapper quartzLogMapper = KitSpringBeanHolder.getBean(SystemQuartzLogMapper.class); + SystemQuartzJobService systemQuartzJobService = KitSpringBeanHolder.getBean(SystemQuartzJobService.class); + KitRedisHelper redisHelper = KitSpringBeanHolder.getBean(KitRedisHelper.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); + // 更新状态 + SystemQuartzJobTb quartzJobTb = KitBeanUtil.copyToClass(quartzJob, SystemQuartzJobTb.class); + systemQuartzJobService.switchQuartzJobStatus(quartzJobTb); + } + if (quartzJob.getEmail() != null) { + SystemEmailService emailService = KitSpringBeanHolder.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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/QuartzManage.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/QuartzManage.java new file mode 100644 index 00000000..69c6c15d --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/quartz/QuartzManage.java @@ -0,0 +1,192 @@ +/* + * Copyright 2021-2026 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 jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.JobBuilder; +import org.quartz.JobDataMap; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.ObjectAlreadyExistsException; +import org.quartz.Scheduler; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; +import org.quartz.impl.triggers.CronTriggerImpl; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * 定时任务管理工具(高耦合版) + * + * @author odboy + */ +@Slf4j +@Component +public class QuartzManage { + + private static final String JOB_NAME = "TASK_"; + @Resource + private Scheduler scheduler; + + public void addJob(SystemQuartzJobTb quartzJob) { + try { + String uuid = JOB_NAME + quartzJob.getId(); + // 构建job信息 + JobDetail jobDetail = JobBuilder.newJob(ExecutionJobBean.class) + .withIdentity(uuid) + .build(); + // 通过触发器名和cron 表达式创建 Trigger + Trigger trigger = TriggerBuilder.newTrigger() + .withIdentity(uuid) + .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 { + String uuid = JOB_NAME + quartzJob.getId(); + TriggerKey triggerKey = TriggerKey.triggerKey(uuid); + 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 { + String uuid = JOB_NAME + quartzJob.getId(); + JobKey jobKey = JobKey.jobKey(uuid); + scheduler.pauseJob(jobKey); + scheduler.deleteJob(jobKey); + } catch (Exception e) { + log.error("删除定时任务失败", e); + throw new BadRequestException("删除定时任务失败"); + } + } + + /** + * 恢复一个job + * + * @param quartzJob / + */ + public void resumeJob(SystemQuartzJobTb quartzJob) { + try { + String uuid = JOB_NAME + quartzJob.getId(); + TriggerKey triggerKey = TriggerKey.triggerKey(uuid); + CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); + // 如果不存在则创建一个定时任务 + if (trigger == null) { + addJob(quartzJob); + } + JobKey jobKey = JobKey.jobKey(uuid); + scheduler.resumeJob(jobKey); + } catch (Exception e) { + log.error("恢复定时任务失败", e); + throw new BadRequestException("恢复定时任务失败"); + } + } + + /** + * 立即执行job + * + * @param quartzJob / + */ + public void runJobNow(SystemQuartzJobTb quartzJob) { + try { + String uuid = JOB_NAME + quartzJob.getId(); + TriggerKey triggerKey = TriggerKey.triggerKey(uuid); + 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(uuid); + scheduler.triggerJob(jobKey, dataMap); + } catch (Exception e) { + log.error("定时任务执行失败", e); + throw new BadRequestException("定时任务执行失败"); + } + } + + /** + * 暂停一个job + * + * @param quartzJob / + */ + public void pauseJob(SystemQuartzJobTb quartzJob) { + try { + String uuid = JOB_NAME + quartzJob.getId(); + JobKey jobKey = JobKey.jobKey(uuid); + scheduler.pauseJob(jobKey); + } catch (Exception e) { + log.error("定时任务暂停失败", e); + throw new BadRequestException("定时任务暂停失败"); + } + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/OssStorageConfiguration.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/OssStorageConfiguration.java new file mode 100644 index 00000000..5c2a8737 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/OssStorageConfiguration.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/local/MultipartConfig.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/local/MultipartConfig.java new file mode 100644 index 00000000..dbddbefd --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/local/MultipartConfig.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2026 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.constant.SystemConst; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.properties.AppProperties; +import jakarta.servlet.MultipartConfigElement; +import java.io.File; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.servlet.MultipartConfigFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.unit.DataSize; + +@Slf4j +@Configuration +public class MultipartConfig { + + @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") + "/" + SystemConst.CURRENT_APP_NAME + "/tmp"; + File tmpFile = new File(location); + if (!tmpFile.exists()) { + if (!tmpFile.mkdirs()) { + log.error("创建临时文件失败"); + throw new BadRequestException("创建临时文件失败"); + } + } + factory.setLocation(location); + return factory.createMultipartConfig(); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/minio/MinioRepository.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/minio/MinioRepository.java new file mode 100644 index 00000000..fc55cb47 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/framework/storage/minio/MinioRepository.java @@ -0,0 +1,372 @@ +/* + * Copyright 2021-2026 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.KitDateUtil; +import cn.odboy.util.KitFileUtil; +import io.minio.BucketExistsArgs; +import io.minio.GetObjectArgs; +import io.minio.GetObjectResponse; +import io.minio.GetPresignedObjectUrlArgs; +import io.minio.ListObjectsArgs; +import io.minio.MakeBucketArgs; +import io.minio.MinioClient; +import io.minio.PutObjectArgs; +import io.minio.RemoveBucketArgs; +import io.minio.RemoveObjectArgs; +import io.minio.Result; +import io.minio.http.Method; +import io.minio.messages.Bucket; +import io.minio.messages.Item; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +@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 = KitFileUtil.getPrefix(tempFile); + String suffix = KitFileUtil.getSuffix(tempFile); + String fileCode = IdUtil.fastSimpleUUID(); + String fileName = fileCode + FileUtil.getSuffix(originalFilename); + String objectName = KitDateUtil.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 = KitFileUtil.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 { + KitFileUtil.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(); + KitFileUtil.checkSize(properties.getOss().getMaxSize(), size); + String fileName = UUID.randomUUID() + FileUtil.getSuffix(originalFilename); + // yyyy/MM/dd/fileName.xxx + String objectName = KitDateUtil.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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/job/TestTask.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/job/TestTask.java new file mode 100644 index 00000000..e2bfb4af --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/job/TestTask.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2026 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-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/openapi/OpenCaptchaController.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/openapi/OpenCaptchaController.java new file mode 100644 index 00000000..7b299d4a --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/openapi/OpenCaptchaController.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2026 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.openapi; + +import cn.odboy.annotation.AnonymousPostMapping; +import cn.odboy.system.dal.model.response.SystemCaptchaVo; +import cn.odboy.system.service.SystemCaptchaService; +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.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/captcha") +@Api(tags = "系统:验证码管理") +public class OpenCaptchaController { + + @Autowired + private SystemCaptchaService systemCaptchaService; + + @ApiOperation("获取验证码") + @AnonymousPostMapping(value = "/getCode") + public ResponseEntity getCode() { + SystemCaptchaVo imgResult = systemCaptchaService.getLoginCaptcha(); + return ResponseEntity.ok(imgResult); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemAuthService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemAuthService.java new file mode 100644 index 00000000..380d4be7 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemAuthService.java @@ -0,0 +1,120 @@ +/* + * Copyright 2021-2026 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.constant.SystemConst; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.system.dal.model.request.SystemUserLoginArgs; +import cn.odboy.system.dal.model.response.SystemAuthVo; +import cn.odboy.system.dal.model.response.SystemUserInfoVo; +import cn.odboy.system.dal.model.response.SystemUserJwtVo; +import cn.odboy.system.dal.redis.SystemUserOnlineInfoDAO; +import cn.odboy.system.framework.permission.core.KitSecurityHelper; +import cn.odboy.system.framework.permission.core.handler.TokenProvider; +import cn.odboy.system.framework.permission.core.handler.UserDetailsHandler; +import cn.odboy.util.KitBeanUtil; +import cn.odboy.util.KitRsaEncryptUtil; +import cn.odboy.util.KitValidUtil; +import org.springframework.beans.factory.annotation.Autowired; +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.stereotype.Service; +import jakarta.servlet.http.HttpServletRequest; + +/** + * 用户认证 + */ +@Service +public class SystemAuthService { + + @Autowired + private AppProperties properties; + @Autowired + private SystemUserOnlineInfoDAO systemUserOnlineInfoDAO; + @Autowired + private TokenProvider tokenProvider; + @Autowired + private PasswordEncoder passwordEncoder; + @Autowired + private UserDetailsHandler userDetailsService; + @Autowired + private SystemCaptchaService systemCaptchaService; + + /** + * 登录 + * + * @param loginArgs / + * @param request / + */ + public SystemAuthVo doLogin(SystemUserLoginArgs loginArgs, HttpServletRequest request) throws Exception { + KitValidUtil.isNull(loginArgs); + + // 密码解密 + String password = KitRsaEncryptUtil.decryptByPrivateKey(properties.getRsa().getPrivateKey(), loginArgs.getPassword()); + // 校验验证码 + systemCaptchaService.validate(loginArgs.getUuid(), loginArgs.getCode()); + // 查询用户信息 + SystemUserJwtVo jwtUser = userDetailsService.loadUserByUsername(loginArgs.getUsername()); + + // 验证用户密码 + KitValidUtil.isTrue(!passwordEncoder.matches(password, jwtUser.getPassword()), "登录密码错误"); + + // 登录验证 + Authentication authentication = + new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + + // 生成令牌 + String token = tokenProvider.createToken(jwtUser); + // 返回 token 与 用户信息 + SystemAuthVo authInfo = new SystemAuthVo(); + authInfo.setToken(String.format("%s %s", SystemConst.TOKEN_PREFIX, token)); + /// 这里是为了清空密码 + authInfo.setUser(KitBeanUtil.copyToClass(jwtUser, SystemUserInfoVo.class)); + + if (properties.getLogin().isSingle()) { + // 踢掉之前已经登录的token + systemUserOnlineInfoDAO.kickOutByUsername(loginArgs.getUsername()); + } + // 保存在线信息 + systemUserOnlineInfoDAO.saveUserJwtModelByToken(jwtUser, token, request); + // 返回登录信息 + return authInfo; + } + + /** + * 查询当前登录人信息 + */ + public SystemUserJwtVo getCurrentUserInfo() { + SystemUserJwtVo jwtUser = (SystemUserJwtVo) KitSecurityHelper.getCurrentUser(); + if (jwtUser.getUser() != null) { + jwtUser.getUser().setPassword(null); + } + return jwtUser; + } + + /** + * 退出登录 + * + * @param request / + */ + public void doLogout(HttpServletRequest request) { + String token = tokenProvider.getToken(request); + systemUserOnlineInfoDAO.logoutByToken(token); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemCaptchaService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemCaptchaService.java new file mode 100644 index 00000000..d806ed99 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemCaptchaService.java @@ -0,0 +1,79 @@ +/* + * Copyright 2021-2026 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.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.constant.CaptchaCodeEnum; +import cn.odboy.constant.SystemConst; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.redis.KitRedisHelper; +import cn.odboy.system.constant.SystemCacheKey; +import cn.odboy.system.dal.model.response.SystemCaptchaVo; +import cn.odboy.util.KitValidUtil; +import com.wf.captcha.base.Captcha; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.concurrent.TimeUnit; + +/** + * 验证码 + */ +@Service +public class SystemCaptchaService { + + @Autowired + private KitRedisHelper redisHelper; + @Autowired + private AppProperties properties; + + /** + * 查询登录验证码 + */ + public SystemCaptchaVo getLoginCaptcha() { + // 查询运算的结果 + 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); + // 验证码信息 + SystemCaptchaVo captchaVo = new SystemCaptchaVo(); + captchaVo.setUuid(uuid); + captchaVo.setImg(captcha.toBase64()); + return captchaVo; + } + + /** + * 验证验证码 + * + * @param uuid 验证码uuid + * @param inputCode 用户输入的验证码 + */ + public void validate(String uuid, String inputCode) { + // 查询验证码 + String code = redisHelper.get(uuid, String.class); + KitValidUtil.isBlank(code, "验证码不存在或已过期"); + // 清除验证码 + redisHelper.del(uuid); + KitValidUtil.isTrue(StrUtil.isBlank(inputCode) || !code.equalsIgnoreCase(inputCode), "验证码错误"); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDataService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDataService.java new file mode 100644 index 00000000..4e0d6d09 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDataService.java @@ -0,0 +1,96 @@ +/* + * Copyright 2021-2026 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.model.response.SystemUserVo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 数据权限 + * + * @author odboy + */ +@Service +public class SystemDataService { + + @Autowired + private SystemUserRoleService systemUserRoleService; + @Autowired + private SystemDeptService systemDeptService; + @Autowired + private SystemRoleDeptService systemRoleDeptService; + + /** + * 查询数据权限 + * + * @param user / + * @return / + */ + public List queryDeptIdByArgs(SystemUserVo user) { + List deptIds = new ArrayList<>(); + // 查询用户角色 + Set roleList = systemUserRoleService.listUserRoleByUserId(user.getId()); + // 查询对应的部门ID + for (SystemRoleTb role : roleList) { + SystemDataScopeEnum dataScopeEnum = SystemDataScopeEnum.find(role.getDataScope()); + switch (Objects.requireNonNull(dataScopeEnum)) { + case THIS_LEVEL: + Long currentDeptId = user.getDept().getId(); + deptIds.add(currentDeptId); + break; + case CUSTOMIZE: + // 优化 283ms -> 51 ms + List customDeptIds = this.queryCustomDataPermissionByArgs(deptIds, role.getId()); + deptIds.addAll(customDeptIds); + break; + default: + return new ArrayList<>(); + } + } + return deptIds.stream().distinct().collect(Collectors.toList()); + } + + /** + * 查询自定义的数据权限 + * + * @param deptIds 部门ID(外部数据) + * @param roleId 角色ID + * @return 数据权限ID(所有部门ID数据) + */ + private List queryCustomDataPermissionByArgs(List deptIds, Long roleId) { + Set deptList = systemRoleDeptService.listUserDeptByRoleId(roleId); + Map> deptPidMap = systemDeptService.listPidNonNull().stream().collect(Collectors.groupingBy(SystemDeptTb::getPid)); + for (SystemDeptTb dept : deptList) { + deptIds.add(dept.getId()); + List deptChildren = deptPidMap.getOrDefault(dept.getId(), null); + if (CollUtil.isNotEmpty(deptChildren)) { + deptIds.addAll(systemDeptService.queryChildDeptIdByDeptIds(deptChildren, deptPidMap)); + } + } + return deptIds; + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDeptService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDeptService.java new file mode 100644 index 00000000..50fddd84 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDeptService.java @@ -0,0 +1,422 @@ +/* + * Copyright 2021-2026 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.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.KitPageResult; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.system.constant.SystemDataScopeEnum; +import cn.odboy.system.constant.SystemZhConst; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.model.export.SystemDeptExportRowVo; +import cn.odboy.system.dal.model.request.SystemCreateDeptArgs; +import cn.odboy.system.dal.model.request.SystemQueryDeptArgs; +import cn.odboy.system.dal.model.response.SystemDeptVo; +import cn.odboy.system.dal.mysql.SystemDeptMapper; +import cn.odboy.system.framework.permission.core.KitSecurityHelper; +import cn.odboy.util.KitBeanUtil; +import cn.odboy.util.KitClassUtil; +import cn.odboy.util.KitValidUtil; +import cn.odboy.util.xlsx.KitExcelExporter; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class SystemDeptService { + + @Autowired + private SystemDeptMapper systemDeptMapper; + @Autowired + private SystemUserDeptService systemUserDeptService; + @Autowired + private SystemRoleDeptService systemRoleDeptService; + + /** + * 创建 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void saveDept(SystemCreateDeptArgs args) { + KitValidUtil.isNull(args); + + SystemDeptTb record = KitBeanUtil.copyToClass(args, SystemDeptTb.class); + systemDeptMapper.insert(record); + + this.updateDeptSubCnt(args.getPid()); + } + + /** + * 编辑 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void updateDeptById(SystemDeptTb args) { + KitValidUtil.isNull(args); + // 旧的父部门 + Long oldPid = this.getDeptById(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.updateById(args); + this.updateDeptSubCnt(oldPid); + this.updateDeptSubCnt(newPid); + } + + /** + * 删除 + * + * @param ids / + */ + @Transactional(rollbackFor = Exception.class) + public void deleteDeptByIds(Set ids) { + // 查询部门, 和其所有子部门 + Set depts = this.traverseDeptByIdWithPids(ids); + // 验证是否被角色或用户关联 + Set deptIds = depts.stream().map(SystemDeptTb::getId).collect(Collectors.toSet()); + KitValidUtil.isTrue(systemUserDeptService.countUserByDeptIds(deptIds) > 0, "所选部门存在用户关联,请解除后再试"); + KitValidUtil.isTrue(systemRoleDeptService.countRoleByDeptIds(deptIds) > 0, "所选部门存在角色关联,请解除后再试"); + for (SystemDeptTb dept : depts) { + systemDeptMapper.deleteById(dept.getId()); + this.updateDeptSubCnt(dept.getPid()); + } + } + + /** + * 更新父节点中子节点数目 + * + * @param deptId 部门id + */ + @Transactional(rollbackFor = Exception.class) + public void updateDeptSubCnt(Long deptId) { + if (deptId != null) { + long count = this.countDeptByPid(deptId); + systemDeptMapper.updateDeptSubCountById(count, deptId); + } + } + + /** + * 查询所有数据 + * + * @param args / + * @param isQuery / + * @return / + * @throws Exception / + */ + public List queryAllDeptByArgs(SystemQueryDeptArgs args, Boolean isQuery) throws Exception { + String dataScopeType = KitSecurityHelper.getDataScopeType(); + if (isQuery) { + if (dataScopeType.equals(SystemDataScopeEnum.ALL.getValue())) { + args.setPidIsNull(true); + } + List fields = KitClassUtil.getAllFields(args.getClass(), new ArrayList<>()); + List fieldNames = new ArrayList<>() {{ + add("pidIsNull"); + add("enabled"); + }}; + for (Field field : fields) { + // 设置对象的访问权限, 保证对private的属性的访问 + field.setAccessible(true); + Object val = field.get(args); + if (fieldNames.contains(field.getName())) { + continue; + } + if (ObjectUtil.isNotNull(val)) { + args.setPidIsNull(null); + break; + } + } + } + // 数据权限 + args.setIds(KitSecurityHelper.getCurrentUserDataScope()); + List list = this.queryDeptByArgs(args); + // 如果为空, 就代表为自定义权限或者本级权限, 就需要去重, 不理解可以注释掉,看查询结果 + if (StrUtil.isBlank(dataScopeType)) { + return this.filterRootDeptList(list); + } + return list; + } + + /** + * 根据部门id遍历所有部门和子部门id集合 + * + * @param deptList 部门集合 + * @param deptPidMap 父部门id和子部门集合的映射关系 + */ + public List queryChildDeptIdByDeptIds(List deptList, Map> deptPidMap) { + List list = new ArrayList<>(); + for (SystemDeptTb systemDeptTb : deptList) { + if (systemDeptTb != null && systemDeptTb.getEnabled()) { + List deptList1 = deptPidMap.getOrDefault(systemDeptTb.getId(), null); + if (CollUtil.isNotEmpty(deptList1)) { + list.addAll(this.queryChildDeptIdByDeptIds(deptList1, deptPidMap)); + } + list.add(systemDeptTb.getId()); + } + } + return list; + } + + /** + * 构建树形数据 + * + * @return / + */ + public KitPageResult searchDeptTree(List ids, Boolean exclude) { + Map allDeptMap = this.getAllDeptMap(); + Set deptSet1 = new LinkedHashSet<>(); + for (Long id : ids) { + // 同级数据 + SystemDeptTb dept = allDeptMap.get(id); + // 上级数据 + List depts = this.querySuperiorDeptByPid(dept, new ArrayList<>(), allDeptMap); + 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())).toList(); + } + deptSet1.addAll(depts); + } + List deptList = new ArrayList<>(deptSet1); + // 构建部门树 + Set trees = new LinkedHashSet<>(); + Set deptSet = new LinkedHashSet<>(); + List deptNames = deptList.stream().map(SystemDeptTb::getName).toList(); + + boolean isChild; + List systemDeptVos = KitBeanUtil.copyToList(deptList, SystemDeptVo.class); + for (SystemDeptVo dept : systemDeptVos) { + isChild = false; + if (dept.getPid() == null) { + trees.add(dept); + } + for (SystemDeptVo it : systemDeptVos) { + 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(allDeptMap.get(dept.getPid()).getName())) { + deptSet.add(dept); + } + } + if (CollUtil.isEmpty(trees)) { + trees = deptSet; + } + KitPageResult baseResult = new KitPageResult<>(); + baseResult.setContent(CollUtil.isEmpty(trees) ? new ArrayList<>(deptSet) : new ArrayList<>(trees)); + baseResult.setTotalElements(deptSet.size()); + return baseResult; + } + + private Map getAllDeptMap() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + return systemDeptMapper.selectList(wrapper).stream().collect(Collectors.toMap(SystemDeptTb::getId, i -> i, (m, n) -> m)); + } + + /** + * 根据部门id遍历所有部门和子部门 + * + * @param ids 部门id集合 + */ + public Set traverseDeptByIdWithPids(Set ids) { + Set depts = new HashSet<>(); + for (Long id : ids) { + // 根部门 + depts.add(systemDeptMapper.selectById(id)); + // 子部门 + List deptList = this.listDeptByPid(id); + if (CollUtil.isNotEmpty(deptList)) { + this.queryRelationDeptByArgs(deptList, depts); + } + } + return depts; + } + + /** + * 根据父部门id查询部门集合 + * + * @param pid 父部门id + * @return / + */ + public List listDeptByPid(long pid) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemDeptTb::getPid, pid); + return systemDeptMapper.selectList(wrapper); + } + + /** + * 导出部门数据 + * + * @param response / + * @param args / + */ + public void exportDeptXlsx(HttpServletResponse response, SystemQueryDeptArgs args) throws Exception { + List systemDeptTbs = this.queryAllDeptByArgs(args, false); + List rowVos = new ArrayList<>(); + for (SystemDeptTb dataObject : systemDeptTbs) { + SystemDeptExportRowVo rowVo = new SystemDeptExportRowVo(); + rowVo.setName(dataObject.getName()); + rowVo.setEnabled(dataObject.getEnabled() ? SystemZhConst.ENABLE_STR : SystemZhConst.DISABLE_STR); + rowVo.setCreateTime(dataObject.getCreateTime()); + rowVos.add(rowVo); + } + KitExcelExporter.exportSimple(response, "部门数据", SystemDeptExportRowVo.class, rowVos); + } + + /** + * 查询父部门非空且启用的部门数据 + * + * @return / + */ + public List listPidNonNull() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemDeptTb::getEnabled, 1); + wrapper.isNotNull(SystemDeptTb::getPid); + return systemDeptMapper.selectList(wrapper); + } + + /** + * 查询部门下所有关联的部门 + * + * @param deptTbList / + * @param depts / + */ + private void queryRelationDeptByArgs(List deptTbList, Set depts) { + for (SystemDeptTb dept : deptTbList) { + depts.add(dept); + List deptList = this.listDeptByPid(dept.getId()); + if (CollUtil.isNotEmpty(deptList)) { + this.queryRelationDeptByArgs(deptList, depts); + } + } + } + + /** + * 根据ID查询同级与上级数据 + * + * @param dept / + * @param deptList / + * @param allDeptMap + * @return / + */ + private List querySuperiorDeptByPid(SystemDeptTb dept, List deptList, Map allDeptMap) { + if (dept.getPid() == null) { + deptList.addAll(this.listRootDept()); + return deptList; + } + deptList.addAll(this.listDeptByPid(dept.getPid())); + return querySuperiorDeptByPid(allDeptMap.get(dept.getPid()), deptList, allDeptMap); + } + + /** + * 查询所有根部门 + * + * @return / + */ + private List listRootDept() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.isNull(SystemDeptTb::getPid); + return systemDeptMapper.selectList(wrapper); + } + + /** + * 根据部门id查询部门信息 + * + * @param id 部门id + * @return / + */ + public SystemDeptTb getDeptById(Long id) { + return systemDeptMapper.selectById(id); + } + + /** + * 根据父部门id统计数量 + * + * @param pid 父部门id + * @return / + */ + private long countDeptByPid(Long pid) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemDeptTb::getPid, pid); + return systemDeptMapper.selectCount(wrapper); + } + + /** + * 筛选出 list 中没有父部门(即 pid 不在其他部门 id 列表中的部门)的部门列表 + */ + public List filterRootDeptList(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 args / + * @return / + */ + public List queryDeptByArgs(SystemQueryDeptArgs args) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (args != null) { + wrapper.in(CollUtil.isNotEmpty(args.getIds()), SystemDeptTb::getId, args.getIds()); + wrapper.like(StrUtil.isNotBlank(args.getName()), SystemDeptTb::getName, args.getName()); + wrapper.eq(args.getEnabled() != null, SystemDeptTb::getEnabled, args.getEnabled()); + wrapper.eq(args.getPid() != null, SystemDeptTb::getPid, args.getPid()); + wrapper.isNull(args.getPidIsNull() != null, SystemDeptTb::getPid); + if (CollUtil.isNotEmpty(args.getCreateTime()) && args.getCreateTime().size() >= 2) { + wrapper.between(SystemDeptTb::getCreateTime, args.getCreateTime().get(0), args.getCreateTime().get(1)); + } + } + wrapper.orderByAsc(SystemDeptTb::getDeptSort); + return systemDeptMapper.selectList(wrapper); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictDetailService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictDetailService.java new file mode 100644 index 00000000..0d9ee16d --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictDetailService.java @@ -0,0 +1,149 @@ +/* + * Copyright 2021-2026 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.util.StrUtil; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemDictDetailTb; +import cn.odboy.system.dal.dataobject.SystemDictTb; +import cn.odboy.system.dal.model.request.SystemCreateDictDetailArgs; +import cn.odboy.system.dal.model.request.SystemQueryDictDetailArgs; +import cn.odboy.system.dal.model.response.SystemDictDetailVo; +import cn.odboy.system.dal.mysql.SystemDictDetailMapper; +import cn.odboy.system.dal.mysql.SystemDictMapper; +import cn.odboy.util.KitBeanUtil; +import cn.odboy.util.KitPageUtil; +import cn.odboy.util.KitValidUtil; +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 java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class SystemDictDetailService { + + @Autowired + private SystemDictDetailMapper systemDictDetailMapper; + @Autowired + private SystemDictMapper systemDictMapper; + + /** + * 创建 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void saveDictDetail(SystemCreateDictDetailArgs args) { + KitValidUtil.isNull(args); + KitValidUtil.isNull(args.getDict()); + SystemDictDetailTb dictDetail = KitBeanUtil.copyToClass(args, SystemDictDetailTb.class); + dictDetail.setDictId(args.getDict().getId()); + systemDictDetailMapper.insert(dictDetail); + } + + /** + * 编辑 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void updateDictDetailById(SystemDictDetailTb args) { + SystemDictDetailTb dictDetail = systemDictDetailMapper.selectById(args.getId()); + args.setId(dictDetail.getId()); + systemDictDetailMapper.updateById(args); + } + + /** + * 删除 + * + * @param id / + */ + @Transactional(rollbackFor = Exception.class) + public void deleteDictDetailById(Long id) { + systemDictDetailMapper.deleteById(id); + } + + /** + * 根据字典id集合删除字典明细 + * + * @param ids 字典id集合 + */ + @Transactional(rollbackFor = Exception.class) + public void deleteDictDetailByDictIds(Set ids) { + if (CollUtil.isNotEmpty(ids)) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(SystemDictDetailTb::getDictId, ids); + systemDictDetailMapper.delete(wrapper); + } + } + + /** + * 分页查询 + * + * @param args 条件 + * @param page 分页参数 + * @return / + */ + public KitPageResult searchDictDetail( + SystemQueryDictDetailArgs args, + Page page + ) { + IPage iPage = systemDictDetailMapper.selectPageByArgs(page, args); + List dictIds = + iPage.getRecords().stream().map(SystemDictDetailVo::getDictId).collect(Collectors.toList()); + if (CollUtil.isNotEmpty(dictIds)) { + Map id2ItemMap = + systemDictMapper.selectByIds(dictIds).stream().collect(Collectors.toMap(SystemDictTb::getId, i -> i)); + for (SystemDictDetailVo record : iPage.getRecords()) { + record.setDict(id2ItemMap.get(record.getDictId())); + } + } + return KitPageUtil.toPage(iPage); + } + + /** + * 根据字典名称查询字典详情 + * + * @param name 字典名称 + * @return / + */ + public List listDictDetailByName(String name) { + if (StrUtil.isBlank(name)) { + return new ArrayList<>(); + } + List detailVos = systemDictDetailMapper.listDictDetailByName(name); + if (CollUtil.isNotEmpty(detailVos)) { + SystemDictDetailVo systemDictDetailVo = detailVos.stream().findFirst().orElse(null); + if (systemDictDetailVo != null) { + SystemDictTb systemDictTb = systemDictMapper.selectById(systemDictDetailVo.getDictId()); + if (systemDictTb != null) { + for (SystemDictDetailVo detailVo : detailVos) { + detailVo.setDict(systemDictTb); + } + } + } + } + return detailVos; + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictService.java new file mode 100644 index 00000000..f504c359 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemDictService.java @@ -0,0 +1,166 @@ +/* + * Copyright 2021-2026 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.util.StrUtil; +import cn.odboy.base.KitPageArgs; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemDictTb; +import cn.odboy.system.dal.model.export.SystemDictExportRowVo; +import cn.odboy.system.dal.model.request.SystemCreateDictArgs; +import cn.odboy.system.dal.model.request.SystemQueryDictArgs; +import cn.odboy.system.dal.model.response.SystemDictDetailVo; +import cn.odboy.system.dal.mysql.SystemDictMapper; +import cn.odboy.util.KitBeanUtil; +import cn.odboy.util.KitPageUtil; +import cn.odboy.util.xlsx.KitExcelExporter; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +@Service +public class SystemDictService { + + @Autowired + private SystemDictMapper systemDictMapper; + @Autowired + private SystemDictDetailService systemDictDetailService; + + /** + * 创建 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void saveDict(SystemCreateDictArgs args) { + SystemDictTb dictTb = KitBeanUtil.copyToClass(args, SystemDictTb.class); + systemDictMapper.insert(dictTb); + } + + /** + * 编辑 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void updateDictById(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 deleteDictByIds(Set ids) { + // 删除字典 + systemDictMapper.deleteByIds(ids); + // 删除字典详情 + systemDictDetailService.deleteDictDetailByDictIds(ids); + } + + /** + * 分页查询 + * + * @param args 条件 + * @param page 分页参数 + * @param orderBy 排序参数 + * @return / + */ + public KitPageResult searchDict(SystemQueryDictArgs args, Page page, KitPageArgs.OrderBy orderBy) { + QueryWrapper wrapper = new QueryWrapper<>(); + if (orderBy != null) { + orderBy.bindWrapper(wrapper); + } + LambdaQueryWrapper lambda = wrapper.lambda(); + this.injectQueryParams(args, lambda); + Page selectPage = systemDictMapper.selectPage(page, lambda); + return KitPageUtil.toPage(selectPage); + } + + /** + * 复杂条件查询字典列表 + * + * @param args / + * @return / + */ + public List queryDictByArgs(SystemQueryDictArgs args) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.injectQueryParams(args, wrapper); + return systemDictMapper.selectList(wrapper); + } + + /** + * 导出字典数据 + * + * @param response / + * @param args / + */ + public void exportDictXlsx(HttpServletResponse response, SystemQueryDictArgs args) { + List systemDictTbs = this.queryDictByArgs(args); + List rowVos = new ArrayList<>(); + for (SystemDictTb dataObject : systemDictTbs) { + List dictDetails = systemDictDetailService.listDictDetailByName(dataObject.getName()); + if (CollUtil.isEmpty(dictDetails)) { + SystemDictExportRowVo rowVo = new SystemDictExportRowVo(); + rowVo.setName(dataObject.getName()); + rowVo.setDescription(dataObject.getDescription()); + rowVo.setCreateTime(dataObject.getCreateTime()); + rowVo.setLabel(""); + rowVo.setValue(""); + rowVos.add(rowVo); + continue; + } + for (SystemDictDetailVo dictDetail : dictDetails) { + SystemDictExportRowVo rowVo = new SystemDictExportRowVo(); + rowVo.setName(dataObject.getName()); + rowVo.setDescription(dataObject.getDescription()); + rowVo.setCreateTime(dataObject.getCreateTime()); + rowVo.setLabel(dictDetail.getLabel()); + rowVo.setValue(dictDetail.getValue()); + rowVos.add(rowVo); + } + } + KitExcelExporter.exportSimple(response, "字典数据", SystemDictExportRowVo.class, rowVos); + } + + /** + * 构建查询条件 + * + * @param args / + * @param wrapper / + */ + private void injectQueryParams(SystemQueryDictArgs args, LambdaQueryWrapper wrapper) { + if (args != null) { + wrapper.and( + StrUtil.isNotBlank(args.getBlurry()), + c -> c.like(SystemDictTb::getName, args.getBlurry()).or().like(SystemDictTb::getDescription, args.getBlurry()) + ); + } + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemEmailService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemEmailService.java new file mode 100644 index 00000000..a020c057 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemEmailService.java @@ -0,0 +1,157 @@ +/* + * Copyright 2021-2026 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.constant.SystemConst; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.redis.KitRedisHelper; +import cn.odboy.system.constant.SystemCaptchaBizEnum; +import cn.odboy.system.dal.dataobject.SystemEmailConfigTb; +import cn.odboy.system.dal.model.request.SystemCheckEmailCaptchaArgs; +import cn.odboy.system.dal.model.request.SystemSendEmailArgs; +import cn.odboy.system.dal.mysql.SystemEmailConfigMapper; +import cn.odboy.util.KitDesEncryptUtil; +import cn.odboy.util.KitResourceTemplateUtil; +import cn.odboy.util.KitValidUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +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 KitRedisHelper redisHelper; + @Autowired + private AppProperties properties; + + /** + * 更新邮件配置 + * + * @param emailConfig 邮箱配置 + */ + @Transactional(rollbackFor = Exception.class) + public void updateEmailConfigById(SystemEmailConfigTb emailConfig) throws Exception { + SystemEmailConfigTb systemEmailConfigTb = getLastEmailConfig(); + if (!emailConfig.getPassword().equals(systemEmailConfigTb.getPassword())) { + // 对称加密 + systemEmailConfigTb.setPassword(KitDesEncryptUtil.desEncrypt(emailConfig.getPassword())); + } + systemEmailConfigTb.setId(1L); + systemEmailConfigMapper.insertOrUpdate(systemEmailConfigTb); + } + + /** + * 发送邮件 + * + * @param sendEmailRequest 邮件发送的内容 + */ + public void sendEmail(SystemSendEmailArgs sendEmailRequest) { + SystemEmailConfigTb systemEmailConfigTb = getLastEmailConfig(); + KitValidUtil.isNull(systemEmailConfigTb.getId(), "请先配置, 再操作"); + // 封装 + 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(KitDesEncryptUtil.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 sendCaptcha(SystemCaptchaBizEnum biEnum, String email) { + KitValidUtil.isNull(biEnum, "biEnum必填"); + String content; + String redisKey = biEnum.getRedisKey() + email; + String oldCode = redisHelper.get(redisKey, String.class); + if (oldCode == null) { + String code = RandomUtil.randomNumbers(6); + // 存入缓存 + KitValidUtil.isTrue(!redisHelper.set(redisKey, code, properties.getCaptcha().getExpireTime()), "服务异常, 请联系网站负责人"); + // 存在就再次发送原来的验证码 + content = KitResourceTemplateUtil.render(null, biEnum.getTemplateName(), Dict.create().set("code", code)); + } else { + content = KitResourceTemplateUtil.render(null, biEnum.getTemplateName(), Dict.create().set("code", oldCode)); + } + SystemSendEmailArgs sendEmailRequest = + new SystemSendEmailArgs(Collections.singletonList(email), SystemConst.CURRENT_APP_TITLE, content); + sendEmail(sendEmailRequest); + } + + /** + * 查询邮箱配置 + * + * @return / + */ + public SystemEmailConfigTb getLastEmailConfig() { + SystemEmailConfigTb systemEmailConfigTb = systemEmailConfigMapper.selectById(1L); + return systemEmailConfigTb == null ? new SystemEmailConfigTb() : systemEmailConfigTb; + } + + /** + * 校验邮箱验证码 + */ + public void checkEmailCaptcha(SystemCheckEmailCaptchaArgs args) { + SystemCaptchaBizEnum biEnum = SystemCaptchaBizEnum.getByBizCode(args.getBizCode()); + KitValidUtil.isNull(biEnum, "不支持的业务"); + this.checkEmailCaptchaV1(biEnum, args.getEmail(), args.getCode()); + } + + public void checkEmailCaptchaV1(SystemCaptchaBizEnum biEnum, String email, String code) { + String redisKey = biEnum.getRedisKey() + email; + String value = redisHelper.get(redisKey, String.class); + KitValidUtil.isTrue(value == null || !value.equals(code), "无效验证码"); + redisHelper.del(redisKey); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemJobService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemJobService.java new file mode 100644 index 00000000..7f4b2c0c --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemJobService.java @@ -0,0 +1,158 @@ +/* + * Copyright 2021-2026 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.util.StrUtil; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemJobTb; +import cn.odboy.system.dal.model.export.SystemJobExportRowVo; +import cn.odboy.system.dal.model.request.SystemCreateJobArgs; +import cn.odboy.system.dal.model.request.SystemQueryJobArgs; +import cn.odboy.system.dal.mysql.SystemJobMapper; +import cn.odboy.util.KitBeanUtil; +import cn.odboy.util.KitPageUtil; +import cn.odboy.util.KitValidUtil; +import cn.odboy.util.xlsx.KitExcelExporter; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +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 jakarta.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +@Service +public class SystemJobService { + + @Autowired + private SystemJobMapper systemJobMapper; + @Autowired + private SystemUserJobService systemUserJobService; + + /** + * 创建 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void saveJob(SystemCreateJobArgs args) { + KitValidUtil.isTrue(this.existJobWithName(args.getName()), "职位名称已存在"); + SystemJobTb systemJobTb = KitBeanUtil.copyToClass(args, SystemJobTb.class); + systemJobMapper.insert(systemJobTb); + } + + /** + * 编辑 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void updateJobById(SystemJobTb args) { + SystemJobTb job = systemJobMapper.selectById(args.getId()); + KitValidUtil.isTrue(this.existJobWithNameNeSelf(args.getName(), job.getId()), "职位名称已存在"); + args.setId(job.getId()); + systemJobMapper.updateById(args); + } + + /** + * 删除 + * + * @param ids / + */ + @Transactional(rollbackFor = Exception.class) + public void deleteJobByIds(Set ids) { + // 验证是否被用户关联 + KitValidUtil.isTrue(systemUserJobService.countUserByJobIds(ids) > 0, "所选的岗位中存在用户关联, 请解除关联再试"); + systemJobMapper.deleteByIds(ids); + } + + /** + * 分页查询 + * + * @param args 条件 + * @param page 分页参数 + */ + public KitPageResult searchJobByArgs(SystemQueryJobArgs args, Page page) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.injectQueryParams(args, wrapper); + Page selectPage = systemJobMapper.selectPage(page, wrapper); + return KitPageUtil.toPage(selectPage); + } + + /** + * 查询全部数据 + */ + public List queryJobByArgs(SystemQueryJobArgs args) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.injectQueryParams(args, wrapper); + return systemJobMapper.selectList(wrapper); + } + + /** + * 导出岗位数据 + */ + public void exportJobXlsx(HttpServletResponse response, SystemQueryJobArgs args) { + List systemJobTbs = this.queryJobByArgs(args); + List rowVos = new ArrayList<>(); + for (SystemJobTb dataObject : systemJobTbs) { + SystemJobExportRowVo rowVo = new SystemJobExportRowVo(); + rowVo.setName(dataObject.getName()); + rowVo.setEnabled(dataObject.getEnabled() ? "启用" : "停用"); + rowVo.setCreateTime(dataObject.getCreateTime()); + rowVos.add(rowVo); + } + KitExcelExporter.exportSimple(response, "岗位数据", SystemJobExportRowVo.class, rowVos); + } + + /** + * 根据岗位名称查询岗位是否存在 + */ + private boolean existJobWithName(String name) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemJobTb::getName, name); + return systemJobMapper.exists(wrapper); + } + + /** + * 根据岗位名称和岗位id查询岗位是否存在 + */ + private boolean existJobWithNameNeSelf(String name, Long currentJobId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemJobTb::getName, name); + wrapper.ne(SystemJobTb::getId, currentJobId); + return systemJobMapper.exists(wrapper); + } + + /** + * 构建查询条件 + * + * @param args / + * @param wrapper / + */ + private void injectQueryParams(SystemQueryJobArgs args, LambdaQueryWrapper wrapper) { + if (args != null) { + wrapper.like(StrUtil.isNotBlank(args.getName()), SystemJobTb::getName, args.getName()); + wrapper.eq(args.getEnabled() != null, SystemJobTb::getEnabled, args.getEnabled()); + if (CollUtil.isNotEmpty(args.getCreateTime()) && args.getCreateTime().size() >= 2) { + wrapper.between(SystemJobTb::getCreateTime, args.getCreateTime().get(0), args.getCreateTime().get(1)); + } + } + wrapper.orderByDesc(SystemJobTb::getJobSort, SystemJobTb::getId); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemLocalStorageService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemLocalStorageService.java new file mode 100644 index 00000000..20012ed6 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemLocalStorageService.java @@ -0,0 +1,194 @@ +/* + * Copyright 2021-2026 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.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.KitPageResult; +import cn.odboy.constant.FileTypeEnum; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.server.core.KitFileLocalUploadHelper; +import cn.odboy.system.dal.dataobject.SystemLocalStorageTb; +import cn.odboy.system.dal.model.export.SystemLocalStorageExportRowVo; +import cn.odboy.system.dal.model.request.SystemQueryStorageArgs; +import cn.odboy.system.dal.mysql.SystemLocalStorageMapper; +import cn.odboy.util.KitBeanUtil; +import cn.odboy.util.KitFileUtil; +import cn.odboy.util.KitPageUtil; +import cn.odboy.util.KitValidUtil; +import cn.odboy.util.xlsx.KitExcelExporter; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import jakarta.servlet.http.HttpServletResponse; +import java.io.File; +import java.util.Date; +import java.util.List; + +@Slf4j +@Service +public class SystemLocalStorageService { + + @Autowired + private SystemLocalStorageMapper systemLocalStorageMapper; + @Autowired + private KitFileLocalUploadHelper fileUploadPathHelper; + @Autowired + private AppProperties properties; + @Lazy + @Autowired + private SystemLocalStorageService localStorageService; + + /** + * 上传 + * + * @param name 文件名称 + * @param multipartFile 文件 + * @return / + */ + @Transactional(rollbackFor = Exception.class) + public SystemLocalStorageTb uploadFile(String name, MultipartFile multipartFile) { + long size = multipartFile.getSize(); + KitFileUtil.checkSize(properties.getOss().getMaxSize(), size); + String suffix = KitFileUtil.getSuffix(multipartFile.getOriginalFilename()); + String type = KitFileUtil.getFileType(suffix); + String uploadDateStr = DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT); + File file = KitFileUtil.upload(multipartFile, fileUploadPathHelper.getPath() + uploadDateStr + File.separator); + KitValidUtil.isNull(file, "上传失败"); + try { + String formatSize = KitFileUtil.getSize(size); + String prefixName = KitFileUtil.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) { + log.error("上传文件失败", e); + KitFileUtil.del(file); + throw e; + } + } + + /** + * 编辑 + * + * @param args 文件信息 + */ + @Transactional(rollbackFor = Exception.class) + public void updateLocalStorageById(SystemLocalStorageTb args) { + systemLocalStorageMapper.updateById(args); + } + + /** + * 多选删除 + * + * @param ids / + */ + @Transactional(rollbackFor = Exception.class) + public void deleteFileByIds(Long[] ids) { + for (Long id : ids) { + SystemLocalStorageTb storage = systemLocalStorageMapper.selectById(id); + try { + KitFileUtil.del(storage.getPath()); + systemLocalStorageMapper.deleteById(storage); + } catch (IORuntimeException e) { + throw new BadRequestException("删除文件 " + storage.getName() + " 失败"); + } + } + } + + /** + * 分页查询 + * + * @param args 条件 + * @param page 分页参数 + * @return / + */ + public KitPageResult searchLocalStorage(SystemQueryStorageArgs args, Page page) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.injectQueryParams(args, wrapper); + Page selectPage = systemLocalStorageMapper.selectPage(page, wrapper); + return KitPageUtil.toPage(selectPage); + } + + /** + * 查询文件上传记录 + */ + public List queryLocalStorageByArgs(SystemQueryStorageArgs args) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.injectQueryParams(args, wrapper); + return systemLocalStorageMapper.selectList(wrapper); + } + + /** + * 上传图片 + */ + @Transactional(rollbackFor = Exception.class) + public SystemLocalStorageTb uploadPicture(MultipartFile file) { + // 判断文件是否为图片 + String suffix = KitFileUtil.getSuffix(file.getOriginalFilename()); + KitValidUtil.isTrue(!FileTypeEnum.IMAGE.getCode().equals(KitFileUtil.getFileType(suffix)), "只能上传图片"); + return localStorageService.uploadFile(null, file); + } + + /** + * 导出文件上传记录 + */ + public void exportLocalStorageXlsx(HttpServletResponse response, SystemQueryStorageArgs args) { + List systemLocalStorageTbs = this.queryLocalStorageByArgs(args); + List rowVos = KitBeanUtil.copyToList(systemLocalStorageTbs, SystemLocalStorageExportRowVo.class); + KitExcelExporter.exportSimple(response, "文件上传记录数据", SystemLocalStorageExportRowVo.class, rowVos); + } + + /** + * 构建查询条件 + * + * @param args / + * @param wrapper / + */ + private void injectQueryParams(SystemQueryStorageArgs args, LambdaQueryWrapper wrapper) { + if (args != null) { + wrapper.and( + StrUtil.isNotBlank(args.getBlurry()), + c -> c.like(SystemLocalStorageTb::getName, args.getBlurry()).or() + .like(SystemLocalStorageTb::getSuffix, args.getBlurry()).or() + .like(SystemLocalStorageTb::getType, args.getBlurry()).or() + .like(SystemLocalStorageTb::getCreateBy, args.getBlurry()) + ); + if (CollUtil.isNotEmpty(args.getCreateTime()) && args.getCreateTime().size() >= 2) { + wrapper.between(SystemLocalStorageTb::getUpdateTime, args.getCreateTime().get(0), + args.getCreateTime().get(1) + ); + } + } + wrapper.orderByDesc(SystemLocalStorageTb::getId); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMenuService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMenuService.java new file mode 100644 index 00000000..568556b0 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMenuService.java @@ -0,0 +1,581 @@ +/* + * Copyright 2021-2026 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.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.system.constant.SystemTransferProtocolConst; +import cn.odboy.system.constant.SystemYesOrNoChConst; +import cn.odboy.system.dal.dataobject.SystemMenuTb; +import cn.odboy.system.dal.model.export.SystemMenuExportRowVo; +import cn.odboy.system.dal.model.request.SystemQueryMenuArgs; +import cn.odboy.system.dal.model.response.SystemMenuMetaVo; +import cn.odboy.system.dal.model.response.SystemMenuRouterVo; +import cn.odboy.system.dal.model.response.SystemMenuVo; +import cn.odboy.system.dal.mysql.SystemMenuMapper; +import cn.odboy.system.framework.permission.core.KitSecurityHelper; +import cn.odboy.util.KitBeanUtil; +import cn.odboy.util.KitClassUtil; +import cn.odboy.util.KitValidUtil; +import cn.odboy.util.xlsx.KitExcelExporter; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import jakarta.servlet.http.HttpServletResponse; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class SystemMenuService { + + @Autowired + private SystemMenuMapper systemMenuMapper; + @Autowired + private SystemRoleMenuService systemRoleMenuService; + @Autowired + private SystemUserRoleService systemUserRoleService; + @Lazy + @Autowired + private SystemMenuService proxyService; + + /** + * 创建 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void saveMenu(SystemMenuTb args) { + KitValidUtil.isTrue(this.existMenuWithTitle(args.getTitle()), "菜单标题已存在"); + if (StrUtil.isNotBlank(args.getComponentName())) { + KitValidUtil.isTrue(this.existMenuWithComponentName(args.getComponentName()), "菜单组件名称已存在"); + } + if (Long.valueOf(0L).equals(args.getPid())) { + // 顶级目录 + args.setPid(null); + } + if (args.getIFrame()) { + if (!(args.getPath().toLowerCase().startsWith(SystemTransferProtocolConst.PREFIX_HTTP) || args.getPath() + .toLowerCase().startsWith(SystemTransferProtocolConst.PREFIX_HTTPS))) { + throw new BadRequestException(SystemTransferProtocolConst.PREFIX_HTTPS_BAD_REQUEST); + } + } + systemMenuMapper.insert(args); + // 计算子节点数目 + args.setSubCount(0); + // 更新父节点菜单数目 + this.updateMenuSubCnt(args.getPid()); + } + + /** + * 编辑 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void updateMenuById(SystemMenuTb args) { + KitValidUtil.isNullF(args.getId(), "id"); + KitValidUtil.isTrue(args.getId().equals(args.getPid()), "上级不能为自己"); + SystemMenuTb menu = systemMenuMapper.selectById(args.getId()); + if (args.getIFrame()) { + KitValidUtil.isTrue(!(args.getPath().toLowerCase().startsWith(SystemTransferProtocolConst.PREFIX_HTTP) || args.getPath() + .toLowerCase().startsWith(SystemTransferProtocolConst.PREFIX_HTTPS)), SystemTransferProtocolConst.PREFIX_HTTPS_BAD_REQUEST); + } + KitValidUtil.isTrue(this.existMenuWithTitleNeSelf(args.getTitle(), menu.getId()), "菜单标题已存在"); + if (args.getPid().equals(0L)) { + args.setPid(null); + } + // 记录的父节点ID + Long oldPid = menu.getPid(); + Long newPid = args.getPid(); + if (StrUtil.isNotBlank(args.getComponentName())) { + KitValidUtil.isTrue(this.existMenuWithComponentNameNeSelf(args.getTitle(), menu.getId()), "菜单组件名称已存在"); + } + 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.updateById(menu); + // 计算父级菜单节点数目 + this.updateMenuSubCnt(oldPid); + this.updateMenuSubCnt(newPid); + } + + /** + * 删除 + */ + @Transactional(rollbackFor = Exception.class) + public void deleteMenuByIds(Set ids) { + // Set menuSet = new HashSet<>(); + // for (Long id : ids) { + // List menuList = this.listMenuByPid(id); + // menuSet.add(this.getMenuVoById(id)); + // menuSet = this.queryChildMenuByArgs(menuList, menuSet); + // } + // List menuIds = menuSet.stream().map(SystemMenuVo::getId).filter(Objects::nonNull).distinct().collect(Collectors.toList()); + // if (CollUtil.isNotEmpty(menuIds)) { + // systemRoleMenuService.deleteRoleMenuByMenuIds(menuIds); + // systemMenuMapper.deleteByIds(menuIds); + // } + // for (SystemMenuVo menu : menuSet) { + // this.updateMenuSubCnt(menu.getPid()); + // } + // ========= 优化 20260114 ========= + if (CollUtil.isEmpty(ids)) { + return; + } + // 查询所有菜单数据 + List allMenus = systemMenuMapper.listAll(); + // 根据ID集合查询当前菜单和其子菜单 + Set allMenuIds = this.getAllMenuIdsWithChildren(ids, allMenus); + if (CollUtil.isNotEmpty(allMenuIds)) { + // 删除角色菜单关联 + systemRoleMenuService.deleteRoleMenuByMenuIds(allMenuIds); + // 批量删除菜单 + systemMenuMapper.deleteByIds(allMenuIds); + // 更新父级菜单子节点数量 + this.updateParentMenuCounts(allMenuIds, allMenus); + } + } + + @Transactional(rollbackFor = Exception.class) + public void updateMenuSubCnt(Long menuId) { + if (menuId != null) { + long count = this.countMenuByPid(menuId); + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(SystemMenuTb::getId, menuId); + wrapper.set(SystemMenuTb::getSubCount, count); + systemMenuMapper.update(wrapper); + } + } + + /** + * 查询全部数据 + * + * @param args 条件 + * @param isQuery / + * @return / + * @throws Exception / + */ + public List queryAllMenu(SystemQueryMenuArgs args, Boolean isQuery) throws Exception { + if (Boolean.TRUE.equals(isQuery)) { + args.setPidIsNull(true); + List fields = KitClassUtil.getAllFields(args.getClass(), new ArrayList<>()); + for (Field field : fields) { + //设置对象的访问权限, 保证对private的属性的访问 + field.setAccessible(true); + Object val = field.get(args); + if ("pidIsNull".equals(field.getName())) { + continue; + } + // 如果有查询条件, 则不指定pidIsNull + if (ObjectUtil.isNotNull(val)) { + args.setPidIsNull(null); + break; + } + } + } + return this.queryMenuByArgs(args); + } + + /** + * 根据当前用户查询菜单 + * + * @param currentUserId / + * @return / + */ + public List listMenuByUserId(Long currentUserId) { +// List roles = systemUserRoleService.listRoleVoByUsersId(currentUserId); +// Set roleIds = roles.stream().map(SystemRoleVo::getId).collect(Collectors.toSet()); + Set roleIds = systemUserRoleService.listUserRoleIdByUserId(currentUserId); + return systemRoleMenuService.listMenuByRoleIds(roleIds, false); + } + + /** + * 懒加载菜单数据 + * + * @param pid / + * @return / + */ + public List listMenuByPid(Long pid, boolean includeButton) { + List menus; + if (pid != null && !pid.equals(0L)) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemMenuTb::getPid, pid); + if (!includeButton) { + // 是否剔除按钮 + wrapper.ne(SystemMenuTb::getType, 2); + } + wrapper.orderByAsc(SystemMenuTb::getMenuSort); + return systemMenuMapper.selectList(wrapper); + } else { + menus = this.listRootMenu(); + } + return menus; + } + + /** + * 根据ID查询同级与上级数据 + * + * @param menu / + * @param menus / + * @return / + */ + public List querySuperiorMenuByArgs(SystemMenuTb menu, List menus) { + if (menu.getPid() == null) { + menus.addAll(this.listRootMenu()); + return menus; + } + menus.addAll(this.listMenuByPid(menu.getPid(), true)); + return querySuperiorMenuByArgs(this.getMenuById(menu.getPid()), menus); + } + + /** + * 构建菜单树 + * + * @param menus 原始数据 + * @return / + */ + public List buildMenuTree(List menus) { + List systemMenuVos = KitBeanUtil.copyToList(menus, SystemMenuVo.class); + List trees = new ArrayList<>(); + Set ids = new HashSet<>(); + for (SystemMenuVo menu : systemMenuVos) { + if (menu.getPid() == null) { + trees.add(menu); + } + for (SystemMenuVo it : systemMenuVos) { + 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 = systemMenuVos.stream().filter(s -> !ids.contains(s.getId())).collect(Collectors.toList()); + } + return trees; + } + + /** + * 构建菜单树 + * + * @param menus / + * @return / + */ + public List buildMenuVo(List menus) { + List list = new LinkedList<>(); + for (SystemMenuVo menu : menus) { + if (menu != null) { + List menuList = menu.getChildren(); + SystemMenuRouterVo menuVo = new SystemMenuRouterVo(); + 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 (CollUtil.isNotEmpty(menuList)) { + menuVo.setAlwaysShow(true); + menuVo.setRedirect("noredirect"); + menuVo.setChildren(buildMenuVo(menuList)); + // 处理是一级菜单并且没有子菜单的情况 + } else if (menu.getPid() == null) { + SystemMenuRouterVo 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; + } + + public List listMenuByIds(List ids) { + return systemMenuMapper.selectByIds(ids); + } + + public List buildFrontMenus() { + List originMenus = this.listMenuByUserId(KitSecurityHelper.getCurrentUserId()); + List targetMenus = this.buildMenuTree(originMenus); + return this.buildMenuVo(targetMenus); + } + + public Set listChildMenuSetByMenuId(Long id) { +// Set menuSet = new HashSet<>(); +// List menuList = this.listMenuByPid(id); +// menuSet.add(this.getMenuById(id)); +// menuSet = this.queryChildMenuByArgs(menuList, menuSet); +// return menuSet.stream().map(SystemMenuTb::getId).collect(Collectors.toSet()); + List allMenus = systemMenuMapper.listAll(); + return this.getAllMenuIdsWithChildren(CollUtil.newHashSet(id), allMenus); + } + + public List listMenuSuperior(List ids) { + Set menus; + if (CollUtil.isNotEmpty(ids)) { + menus = new LinkedHashSet<>(this.listMenuByIds(ids)); + for (SystemMenuTb menu : menus) { + List menuList = this.querySuperiorMenuByArgs(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()); + List newMenuList = new ArrayList<>(menus); + return this.buildMenuTree(newMenuList); + } else { + List records = this.listMenuByPid(null, true); + return KitBeanUtil.copyToList(records, SystemMenuVo.class); + } + } + + public void exportMenuXlsx(HttpServletResponse response, SystemQueryMenuArgs args) throws Exception { + List systemMenuTbs = this.queryAllMenu(args, false); + List rowVos = new ArrayList<>(); + for (SystemMenuTb dataObject : systemMenuTbs) { + SystemMenuExportRowVo rowVo = new SystemMenuExportRowVo(); + rowVo.setTitle(dataObject.getTitle()); + rowVo.setType(dataObject.getType() == null ? "目录" : dataObject.getType() == 1 ? "菜单" : "按钮"); + rowVo.setPermission(dataObject.getPermission()); + rowVo.setIFrame(dataObject.getIFrame() ? SystemYesOrNoChConst.YES_STR : SystemYesOrNoChConst.NO_STR); + rowVo.setHidden(dataObject.getHidden() ? SystemYesOrNoChConst.NO_STR : SystemYesOrNoChConst.YES_STR); + rowVo.setCache(dataObject.getCache() ? SystemYesOrNoChConst.YES_STR : SystemYesOrNoChConst.NO_STR); + rowVo.setCreateTime(dataObject.getCreateTime()); + rowVos.add(rowVo); + } + KitExcelExporter.exportSimple(response, "菜单数据", SystemMenuExportRowVo.class, rowVos); + } + + /** + * 查询 MenuResponse + * + * @param menu / + * @param menuVo / + * @return / + */ + private SystemMenuRouterVo getMenuVo(SystemMenuVo menu, SystemMenuRouterVo menuVo) { + SystemMenuRouterVo menuVo1 = new SystemMenuRouterVo(); + 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; + } + + private Long countMenuByPid(Long pid) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemMenuTb::getPid, pid); + return systemMenuMapper.selectCount(wrapper); + } + + private List queryMenuByArgs(SystemQueryMenuArgs args) { + KitValidUtil.isNull(args); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.isNull(args.getPidIsNull() != null, SystemMenuTb::getPid); + wrapper.eq(args.getPid() != null, SystemMenuTb::getPid, args.getPid()); + wrapper.and(StrUtil.isNotBlank(args.getBlurry()), c -> c.like(SystemMenuTb::getTitle, args.getBlurry()).or() + .like(SystemMenuTb::getComponentName, args.getBlurry()).or() + .like(SystemMenuTb::getPermission, args.getBlurry())); + if (CollUtil.isNotEmpty(args.getCreateTime()) && args.getCreateTime().size() >= 2) { + wrapper.between(SystemMenuTb::getCreateTime, args.getCreateTime().get(0), args.getCreateTime().get(1)); + } + wrapper.orderByAsc(SystemMenuTb::getMenuSort); + return systemMenuMapper.selectList(wrapper); + } + + /** + * 查询根目录 + */ + private List listRootMenu() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.isNull(SystemMenuTb::getPid); + wrapper.orderByAsc(SystemMenuTb::getMenuSort); + return systemMenuMapper.selectList(wrapper); + } + + /** + * 根据组件名称查询菜单是否存在 + * + * @param componentName 组件名称 + * @return / + */ + private boolean existMenuWithComponentName(String componentName) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemMenuTb::getComponentName, componentName); + return systemMenuMapper.exists(wrapper); + } + + /** + * 根据组件名称查询菜单是否存在 + * + * @param componentName 组件名称 + * @return / + */ + private boolean existMenuWithComponentNameNeSelf(String componentName, Long currentMenuId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemMenuTb::getComponentName, componentName); + wrapper.ne(SystemMenuTb::getId, currentMenuId); + return systemMenuMapper.exists(wrapper); + } + + /** + * 根据菜单标题查询菜单是否存在 + * + * @param title 菜单标题 + * @return / + */ + private boolean existMenuWithTitle(String title) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemMenuTb::getTitle, title); + return systemMenuMapper.exists(wrapper); + } + + /** + * 根据菜单标题查询菜单是否存在 + * + * @param title 菜单标题 + * @return / + */ + private boolean existMenuWithTitleNeSelf(String title, Long currentMenuId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemMenuTb::getTitle, title); + wrapper.ne(SystemMenuTb::getId, currentMenuId); + return systemMenuMapper.exists(wrapper); + } + + /** + * 从所有菜单中找出指定ID及其子菜单ID + * + * @param rootIds 要删除的根菜单ID集合 + * @param allMenus 所有菜单数据 + * @return 包含根菜单及其所有子菜单的ID集合 + */ + private Set getAllMenuIdsWithChildren(Set rootIds, List allMenus) { + Set result = new HashSet<>(rootIds); + Set currentLevelIds = new HashSet<>(rootIds); + // 构建父ID到子菜单的映射 + Map> parentToChildrenMap = allMenus.stream().filter(menu -> menu.getPid() != null) + .collect(Collectors.groupingBy(SystemMenuTb::getPid)); + while (!currentLevelIds.isEmpty()) { + Set nextLevelIds = new HashSet<>(); + // 对于当前层级的每个菜单ID,查找其子菜单 + for (Long parentId : currentLevelIds) { + List children = parentToChildrenMap.get(parentId); + if (children != null && !children.isEmpty()) { + for (SystemMenuTb child : children) { + // 如果成功添加(之前不存在),则继续查找其子菜单 + if (result.add(child.getId())) { + nextLevelIds.add(child.getId()); + } + } + } + } + currentLevelIds = nextLevelIds; + } + return result; + } + + /** + * 更新被删除菜单的父菜单子节点计数 + */ + private void updateParentMenuCounts(Set deletedMenuIds, List allMenus) { + // 查询被删除菜单的父ID + Set parentIds = + allMenus.stream().filter(menu -> deletedMenuIds.contains(menu.getId()) && menu.getPid() != null) + .map(SystemMenuTb::getPid).collect(Collectors.toSet()); + // 更新每个父菜单的子节点计数 + for (Long parentId : parentIds) { + proxyService.updateMenuSubCnt(parentId); + } + } + + /** + * 根据菜单id查询目录 + * + * @param id 菜单id + */ + private SystemMenuTb getMenuById(Long id) { + return systemMenuMapper.selectById(id); + } + + /** + * 查询所有子节点, 包含自身ID + * + * @param menuList / + * @param menuSet / + * @return / + */ + private Set queryChildMenuByArgs(List menuList, Set menuSet) { + for (SystemMenuTb menu : menuList) { + menuSet.add(menu); + List menus = this.listMenuByPid(menu.getId(), true); + if (CollUtil.isNotEmpty(menus)) { + this.queryChildMenuByArgs(menus, menuSet); + } + } + return menuSet; + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMonitorService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMonitorService.java new file mode 100644 index 00000000..6c3f7ee1 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemMonitorService.java @@ -0,0 +1,217 @@ +/* + * Copyright 2021-2026 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.KitFileUtil; +import cn.odboy.util.KitIPUtil; +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.Arrays; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +public class SystemMonitorService { + + private final DecimalFormat df = new DecimalFormat("0.00"); + + /** + * 查询服务器监控信息 + * + * @return Map + */ + public Map getServerMonitorInfo() { + 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 ? KitFileUtil.getSize(total) : "?"); + diskInfo.put("available", KitFileUtil.getSize(available)); + diskInfo.put("used", KitFileUtil.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", KitIPUtil.getLocalIp()); + return systemInfo; + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemOperationLogService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemOperationLogService.java new file mode 100644 index 00000000..4243257c --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemOperationLogService.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2026 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.KitPageArgs; +import cn.odboy.system.dal.dataobject.SystemOperationLogTb; +import cn.odboy.system.dal.model.request.SystemQueryOperationLogArgs; +import cn.odboy.system.dal.mysql.SystemOperationLogMapper; +import cn.odboy.system.framework.permission.core.KitSecurityHelper; +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; + +@Service +public class SystemOperationLogService { + + @Autowired + private SystemOperationLogMapper systemOperationLogMapper; + + /** + * 查询用户操作日志 + */ + public IPage searchUserLog(KitPageArgs pageArgs) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemOperationLogTb::getUsername, KitSecurityHelper.getCurrentUsername()); + Page page = new Page<>(pageArgs.getPage(), pageArgs.getSize()); + return systemOperationLogMapper.selectPage(page, wrapper); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemOssStorageService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemOssStorageService.java new file mode 100644 index 00000000..70cb7717 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemOssStorageService.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2026 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.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemOssStorageTb; +import cn.odboy.system.dal.model.request.SystemQueryStorageArgs; +import cn.odboy.system.dal.model.response.SystemOssStorageVo; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import org.springframework.web.multipart.MultipartFile; +import jakarta.servlet.http.HttpServletResponse; +import java.util.List; + +/** + *

+ * OSS存储接口 + *

+ * + * @author codegen + * @since 2025-07-15 + */ +public interface SystemOssStorageService extends IService { + + /** + * 分页查询 + * + * @param args 查询条件 + * @param page 分页参数 + * @return / + */ + KitPageResult searchOssStorage(SystemQueryStorageArgs args, Page page); + + /** + * 复杂条件查询 + * + * @param args 查询条件 + * @return / + */ + List queryOssStorage(SystemQueryStorageArgs args); + + /** + * 上传文件 + * + * @param file / + * @return / + */ + String uploadFile(MultipartFile file); + + /** + * 根据id删除文件 + * + * @param ids 文件记录id + */ + void deleteFileByIds(Long[] ids); + + /** + * 导出文件上传记录 + * + * @param response / + * @param args 查询条件 + */ + void exportOssStorageXlsx(HttpServletResponse response, SystemQueryStorageArgs args); +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemQuartzJobService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemQuartzJobService.java new file mode 100644 index 00000000..cec3270b --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemQuartzJobService.java @@ -0,0 +1,335 @@ +/* + * Copyright 2021-2026 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.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.KitPageResult; +import cn.odboy.framework.context.KitSpringBeanHolder; +import cn.odboy.framework.exception.BadRequestException; +import cn.odboy.framework.redis.KitRedisHelper; +import cn.odboy.system.dal.dataobject.SystemQuartzJobTb; +import cn.odboy.system.dal.dataobject.SystemQuartzLogTb; +import cn.odboy.system.dal.model.export.SystemQuartzJobExportRowVo; +import cn.odboy.system.dal.model.export.SystemQuartzLogExportRowVo; +import cn.odboy.system.dal.model.request.SystemQueryQuartzJobArgs; +import cn.odboy.system.dal.model.request.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.KitBeanUtil; +import cn.odboy.util.KitPageUtil; +import cn.odboy.util.KitValidUtil; +import cn.odboy.util.xlsx.KitExcelExporter; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.jetbrains.annotations.NotNull; +import org.quartz.CronExpression; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import jakarta.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +@Service +public class SystemQuartzJobService { + + @Autowired + private SystemQuartzJobMapper systemQuartzJobMapper; + @Autowired + private SystemQuartzLogMapper systemQuartzLogMapper; + @Autowired + private QuartzManage quartzManage; + @Autowired + private KitRedisHelper redisHelper; + + /** + * 创建 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void createJob(SystemQuartzJobTb args) { + // 验证Bean是不是合法的, 合法的定时任务 Bean 需要用 @Service 定义 + checkBean(args.getBeanName()); + if (!CronExpression.isValidExpression(args.getCronExpression())) { + throw new BadRequestException("cron表达式格式错误"); + } + systemQuartzJobMapper.insert(args); + quartzManage.addJob(args); + } + + /** + * 修改任务并重新调度 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void updateQuartzJobResumeCron(SystemUpdateQuartzJobArgs args) { + // 验证Bean是不是合法的, 合法的定时任务 Bean 需要用 @Service 定义 + checkBean(args.getBeanName()); + KitValidUtil.isTrue(!CronExpression.isValidExpression(args.getCronExpression()), "Cron表达式格式错误"); + if (StrUtil.isNotBlank(args.getSubTask())) { + List tasks = Arrays.asList(args.getSubTask().split("[,,]")); + KitValidUtil.isTrue(tasks.contains(args.getId().toString()), "子任务中不能添加当前任务ID"); + } + SystemQuartzJobTb jobTb = KitBeanUtil.copyToClass(args, SystemQuartzJobTb.class); + systemQuartzJobMapper.updateById(jobTb); + quartzManage.updateJobCron(jobTb); + } + + /** + * 更改定时任务状态 + * + * @param quartzJobTb / + */ + @Transactional(rollbackFor = Exception.class) + public void switchQuartzJobStatus(SystemQuartzJobTb quartzJobTb) { + // 置换暂停状态 + if (quartzJobTb.getIsPause()) { + quartzManage.resumeJob(quartzJobTb); + quartzJobTb.setIsPause(false); + } else { + quartzManage.pauseJob(quartzJobTb); + quartzJobTb.setIsPause(true); + } + systemQuartzJobMapper.updateById(quartzJobTb); + } + + /** + * 立即执行定时任务 + * + * @param quartzJob / + */ + public void startQuartzJob(SystemQuartzJobTb quartzJob) { + quartzManage.runJobNow(quartzJob); + } + + /** + * 删除任务 + * + * @param ids / + */ + @Transactional(rollbackFor = Exception.class) + public void deleteJobByIds(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 quartzJobTb = systemQuartzJobMapper.selectById(id); + if (quartzJobTb == null) { + // 防止子任务不存在 + continue; + } + // 执行任务 + String uuid = IdUtil.simpleUUID(); + quartzJobTb.setUuid(uuid); + startQuartzJob(quartzJobTb); + // 查询执行状态, 如果执行失败则停止后面的子任务执行 + 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 args 条件 + * @param page 分页参数 + * @return / + */ + public KitPageResult searchQuartzJobByArgs(SystemQueryQuartzJobArgs args, Page page) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.injectQuartzJobQueryParams(args, wrapper); + return KitPageUtil.toPage(systemQuartzJobMapper.selectPage(page, wrapper)); + } + + /** + * 分页查询日志 + * + * @param args 条件 + * @param page 分页参数 + * @return / + */ + public KitPageResult searchQuartzLogByArgs(SystemQueryQuartzJobArgs args, Page page) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.injectQuartzLogQueryParams(args, wrapper); + return KitPageUtil.toPage(systemQuartzLogMapper.selectPage(page, wrapper)); + } + + /** + * 查询全部 + * + * @param args 条件 + * @return / + */ + public List queryQuartzJobByArgs(SystemQueryQuartzJobArgs args) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.injectQuartzJobQueryParams(args, wrapper); + return systemQuartzJobMapper.selectList(wrapper); + } + + /** + * 查询全部 + * + * @param args 条件 + * @return / + */ + public List queryQuartzLogByArgs(SystemQueryQuartzJobArgs args) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.injectQuartzLogQueryParams(args, wrapper); + return systemQuartzLogMapper.selectList(wrapper); + } + + /** + * 查询启用的定时任务 + */ + public List listEnableQuartzJob() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemQuartzJobTb::getIsPause, 0); + return systemQuartzJobMapper.selectList(wrapper); + } + + /** + * 导出定时任务数据 + */ + public void exportQuartzJobXlsx(HttpServletResponse response, SystemQueryQuartzJobArgs args) { + List systemQuartzJobTbs = this.queryQuartzJobByArgs(args); + List rowVos = new ArrayList<>(); + for (SystemQuartzJobTb dataObject : systemQuartzJobTbs) { + SystemQuartzJobExportRowVo rowVo = this.getExportRowVo(dataObject); + rowVos.add(rowVo); + } + KitExcelExporter.exportSimple(response, "定时任务数据", SystemQuartzJobExportRowVo.class, rowVos); + } + + @NotNull + private SystemQuartzJobExportRowVo getExportRowVo(SystemQuartzJobTb dataObject) { + SystemQuartzJobExportRowVo rowVo = new SystemQuartzJobExportRowVo(); + rowVo.setJobName(dataObject.getJobName()); + rowVo.setBeanName(dataObject.getBeanName()); + rowVo.setMethodName(dataObject.getMethodName()); + rowVo.setParams(dataObject.getParams()); + rowVo.setCronExpression(dataObject.getCronExpression()); + rowVo.setIsPause(dataObject.getIsPause() ? "暂停中" : "运行中"); + rowVo.setDescription(dataObject.getDescription()); + rowVo.setCreateTime(dataObject.getCreateTime()); + return rowVo; + } + + /** + * 导出定时任务日志数据 + */ + public void exportQuartzLogXlsx(HttpServletResponse response, SystemQueryQuartzJobArgs args) { + List systemQuartzLogTbs = this.queryQuartzLogByArgs(args); + List rowVos = new ArrayList<>(); + for (SystemQuartzLogTb dataObject : systemQuartzLogTbs) { + SystemQuartzLogExportRowVo rowVo = new SystemQuartzLogExportRowVo(); + rowVo.setJobName(dataObject.getJobName()); + rowVo.setBeanName(dataObject.getBeanName()); + rowVo.setMethodName(dataObject.getMethodName()); + rowVo.setParams(dataObject.getParams()); + rowVo.setCronExpression(dataObject.getCronExpression()); + rowVo.setExceptionDetail(dataObject.getExceptionDetail()); + rowVo.setTime(dataObject.getTime()); + rowVo.setStatus(dataObject.getIsSuccess() ? "成功" : "失败"); + rowVo.setCreateTime(dataObject.getCreateTime()); + rowVos.add(rowVo); + } + KitExcelExporter.exportSimple(response, "定时任务日志数据", SystemQuartzLogExportRowVo.class, rowVos); + } + + /** + * 根据任务id查询定时任务 + */ + public SystemQuartzJobTb getQuartzJobById(Long id) { + return systemQuartzJobMapper.selectById(id); + } + + /** + * 构建查询条件 + * + * @param args / + * @param wrapper / + */ + private void injectQuartzJobQueryParams(SystemQueryQuartzJobArgs args, LambdaQueryWrapper wrapper) { + if (args != null) { + wrapper.like(StrUtil.isNotBlank(args.getJobName()), SystemQuartzJobTb::getJobName, args.getJobName()); + if (CollUtil.isNotEmpty(args.getCreateTime()) && args.getCreateTime().size() >= 2) { + wrapper.between(SystemQuartzJobTb::getUpdateTime, args.getCreateTime().get(0), args.getCreateTime().get(1)); + } + } + wrapper.orderByDesc(SystemQuartzJobTb::getId); + } + + /** + * 构建查询条件 + * + * @param args / + * @param wrapper / + */ + private void injectQuartzLogQueryParams(SystemQueryQuartzJobArgs args, LambdaQueryWrapper wrapper) { + if (args != null) { + wrapper.like(StrUtil.isNotBlank(args.getJobName()), SystemQuartzLogTb::getJobName, args.getJobName()); + wrapper.eq(args.getIsSuccess() != null, SystemQuartzLogTb::getIsSuccess, args.getIsSuccess()); + if (CollUtil.isNotEmpty(args.getCreateTime()) && args.getCreateTime().size() >= 2) { + wrapper.between(SystemQuartzLogTb::getCreateTime, args.getCreateTime().get(0), + args.getCreateTime().get(1) + ); + } + } + wrapper.orderByDesc(SystemQuartzLogTb::getId); + } + + /** + * 验证Bean是不是合法的, 合法的定时任务 Bean 需要用 @Service 定义 + * + * @param beanName Bean名称 + */ + private void checkBean(String beanName) { + // 避免调用攻击者可以从SpringContextHolder获得控制jdbcTemplate类 + // 并使用getDeclaredMethod调用jdbcTemplate的queryForMap函数,执行任意sql命令 + KitValidUtil.isTrue(!KitSpringBeanHolder.getAllServiceBeanName().contains(beanName), "非法的 Bean,请重新输入"); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleDeptService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleDeptService.java new file mode 100644 index 00000000..e1c65e9a --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleDeptService.java @@ -0,0 +1,94 @@ +/* + * Copyright 2021-2026 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.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.dataobject.SystemRoleDeptTb; +import cn.odboy.system.dal.mysql.SystemRoleDeptMapper; +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 java.util.ArrayList; +import java.util.List; +import java.util.Set; + +@Service +public class SystemRoleDeptService { + + @Autowired + private SystemRoleDeptMapper systemRoleDeptMapper; + + /** + * 批量删除角色部门关联 + * + * @param roleIds 角色id集合 + */ + @Transactional(rollbackFor = Exception.class) + public void batchDeleteRoleDept(Set roleIds) { + if (CollUtil.isNotEmpty(roleIds)) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(SystemRoleDeptTb::getRoleId, roleIds); + systemRoleDeptMapper.delete(wrapper); + } + } + + /** + * 批量绑定角色部门关联 + * + * @param depts 部门集合 + * @param roleId 角色id + */ + @Transactional(rollbackFor = Exception.class) + public void batchInsertRoleDept(Set depts, Long roleId) { + if (CollUtil.isNotEmpty(depts)) { + List records = new ArrayList<>(); + for (SystemDeptTb dept : depts) { + SystemRoleDeptTb record = new SystemRoleDeptTb(); + record.setRoleId(roleId); + record.setDeptId(dept.getId()); + records.add(record); + } + systemRoleDeptMapper.insert(records); + } + } + + /** + * 根据部门ID统计角色数量 + * + * @param deptIds 部门id集合 + * @return / + */ + public Long countRoleByDeptIds(Set deptIds) { + if (CollUtil.isEmpty(deptIds)) { + return 0L; + } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(SystemRoleDeptTb::getDeptId, deptIds); + return systemRoleDeptMapper.selectCount(wrapper); + } + + /** + * 根据角色id查询部门信息 + * + * @param roleId 角色id + * @return / + */ + public Set listUserDeptByRoleId(Long roleId) { + return systemRoleDeptMapper.listUserDeptByRoleId(roleId); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleMenuService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleMenuService.java new file mode 100644 index 00000000..e27f88c4 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleMenuService.java @@ -0,0 +1,136 @@ +/* + * Copyright 2021-2026 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.util.StrUtil; +import cn.odboy.system.dal.dataobject.SystemMenuTb; +import cn.odboy.system.dal.dataobject.SystemRoleMenuTb; +import cn.odboy.system.dal.mysql.SystemRoleMenuMapper; +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 java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class SystemRoleMenuService { + + @Autowired + private SystemRoleMenuMapper systemRoleMenuMapper; + + /** + * 删除角色菜单关联 + * + * @param roleId 角色id + */ + @Transactional(rollbackFor = Exception.class) + public void deleteRoleMenuByRoleId(Long roleId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemRoleMenuTb::getRoleId, roleId); + systemRoleMenuMapper.delete(wrapper); + } + + /** + * 批量绑定角色菜单关联 + * + * @param menus 菜单集合 + * @param roleId 角色id + */ + @Transactional(rollbackFor = Exception.class) + public void batchInsertRoleMenu(Set menus, Long roleId) { + if (CollUtil.isNotEmpty(menus)) { + List records = new ArrayList<>(); + for (SystemMenuTb menu : menus) { + SystemRoleMenuTb record = new SystemRoleMenuTb(); + record.setMenuId(menu.getId()); + record.setRoleId(roleId); + records.add(record); + } + systemRoleMenuMapper.insert(records); + } + } + + /** + * 批量删除角色菜单关联 + * + * @param roleIds 角色id集合 + */ + @Transactional(rollbackFor = Exception.class) + public void batchDeleteRoleMenu(Set roleIds) { + if (CollUtil.isNotEmpty(roleIds)) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(SystemRoleMenuTb::getRoleId, roleIds); + systemRoleMenuMapper.delete(wrapper); + } + } + + /** + * @param roleIds 角色id集合 + * @return / + */ + public List listMenuByRoleIds(Set roleIds, boolean hasButton) { + if (CollUtil.isEmpty(roleIds)) { + return new ArrayList<>(); + } +// LambdaQueryWrapper roleMenuWrapper = new LambdaQueryWrapper<>(); +// roleMenuWrapper.in(SystemRoleMenuTb::getRoleId, roleIds); +// List menuIds = systemRoleMenuMapper.selectList(roleMenuWrapper).stream().map(SystemRoleMenuTb::getMenuId) +// .collect(Collectors.toList()); +// if (CollUtil.isEmpty(menuIds)) { +// return new ArrayList<>(); +// } +// LambdaQueryWrapper menuWrapper = new LambdaQueryWrapper<>(); +// menuWrapper.in(SystemMenuTb::getId, menuIds); +// // 排除"按钮" +// menuWrapper.ne(SystemMenuTb::getType, 2); +// menuWrapper.orderByAsc(SystemMenuTb::getMenuSort); +// return systemMenuMapper.selectList(menuWrapper); + if (hasButton) { + return systemRoleMenuMapper.listMenuWithRoleIds(roleIds); + } + return systemRoleMenuMapper.listMenuWithRoleIdsNeButton(roleIds); + } + + /** + * 批量删除菜单角色关联 + * + * @param menuIds 菜单id集合 + */ + @Transactional(rollbackFor = Exception.class) + public void deleteRoleMenuByMenuIds(Set menuIds) { + if (CollUtil.isEmpty(menuIds)) { + return; + } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(SystemRoleMenuTb::getMenuId, menuIds); + systemRoleMenuMapper.delete(wrapper); + } + + /** + * 根据角色id集合查询菜单权限 + * + * @param roleIds 角色id集合 + * @return / + */ + public List listMenuPermissionByRoleIds(Set roleIds) { + Set permissions = systemRoleMenuMapper.listMenuPermissionByRoleIds(roleIds); + return permissions.stream().filter(StrUtil::isNotBlank).collect(Collectors.toList()); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleService.java new file mode 100644 index 00000000..4f36282b --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemRoleService.java @@ -0,0 +1,301 @@ +/* + * Copyright 2021-2026 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.lang.Dict; +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.KitPageResult; +import cn.odboy.system.dal.dataobject.SystemMenuTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import cn.odboy.system.dal.model.export.SystemRoleExportRowVo; +import cn.odboy.system.dal.model.request.SystemCreateRoleArgs; +import cn.odboy.system.dal.model.request.SystemQueryRoleArgs; +import cn.odboy.system.dal.model.response.SystemRoleCodeVo; +import cn.odboy.system.dal.model.response.SystemRoleVo; +import cn.odboy.system.dal.model.response.SystemUserVo; +import cn.odboy.system.dal.mysql.SystemRoleMapper; +import cn.odboy.system.framework.permission.core.KitSecurityHelper; +import cn.odboy.util.KitBeanUtil; +import cn.odboy.util.KitPageUtil; +import cn.odboy.util.KitValidUtil; +import cn.odboy.util.xlsx.KitExcelExporter; +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 jakarta.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class SystemRoleService { + + @Autowired + private SystemRoleMapper systemRoleMapper; + @Autowired + private SystemRoleMenuService systemRoleMenuService; + @Autowired + private SystemRoleDeptService systemRoleDeptService; + @Autowired + private SystemUserRoleService systemUserRoleService; + + /** + * 创建 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void saveRole(SystemCreateRoleArgs args) { + this.checkRoleLevels(args.getLevel()); + KitValidUtil.isTrue(this.existRoleWithName(args.getName()), "角色名称已存在"); + SystemRoleTb roleTb = KitBeanUtil.copyToClass(args, SystemRoleTb.class); + systemRoleMapper.insert(roleTb); + // 判断是否有部门数据, 若有, 则需创建关联 + if (CollUtil.isNotEmpty(args.getDepts())) { + systemRoleDeptService.batchInsertRoleDept(args.getDepts(), roleTb.getId()); + } + } + + /** + * 修改 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void updateRoleById(SystemRoleVo args) { + this.checkRoleLevels(args.getLevel()); + SystemRoleTb role = systemRoleMapper.selectById(args.getId()); + KitValidUtil.isTrue(this.existRoleWithNameNeSelf(args.getName(), role.getId()), "角色名称已存在"); + role.setName(args.getName()); + role.setDescription(args.getDescription()); + role.setDataScope(args.getDataScope()); + role.setLevel(args.getLevel()); + // 更新 + systemRoleMapper.updateById(role); + // 删除关联部门数据 + systemRoleDeptService.batchDeleteRoleDept(Collections.singleton(args.getId())); + // 判断是否有部门数据, 若有, 则需更新关联 + if (CollUtil.isNotEmpty(args.getDepts())) { + systemRoleDeptService.batchInsertRoleDept(args.getDepts(), args.getId()); + } + } + + /** + * 修改绑定的菜单 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void updateBindMenuById(SystemRoleVo args) { + SystemRoleTb role = systemRoleMapper.selectById(args.getId()); + this.checkRoleLevels(role.getLevel()); + // 更新菜单 + systemRoleMenuService.deleteRoleMenuByRoleId(role.getId()); + Set menus = args.getMenus(); + if (CollUtil.isNotEmpty(menus)) { + systemRoleMenuService.batchInsertRoleMenu(menus, role.getId()); + } + } + + /** + * 删除 + * + * @param ids / + */ + @Transactional(rollbackFor = Exception.class) + public void deleteRoleByIds(Set ids) { + List roleLevels = systemRoleMapper.listRoleLevelByRoleIds(new ArrayList<>(ids)); + for (Integer roleLevel : roleLevels) { + this.checkRoleLevels(roleLevel); + } + // 验证是否被用户关联 + KitValidUtil.isTrue(systemUserRoleService.existUserWithRoleIds(ids), "所选角色存在用户关联, 请解除关联再试"); + // 删除角色 + systemRoleMapper.deleteByIds(ids); + // 删除角色部门关联数据、角色菜单关联数据 + systemRoleDeptService.batchDeleteRoleDept(ids); + systemRoleMenuService.batchDeleteRoleMenu(ids); + } + + /** + * 查询全部角色 + */ + public List listAllRole() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.orderByDesc(SystemRoleTb::getCreateTime); + return systemRoleMapper.selectList(wrapper); + } + + /** + * 根据条件查询全部角色 + * + * @param args 条件 + * @param rolePage 分页参数 + * @return / + */ + public List queryRoleByArgs(SystemQueryRoleArgs args, Page rolePage) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.injectQueryParams(args, wrapper); + return systemRoleMapper.selectList(wrapper); + } + + /** + * 分页查询角色 + * + * @param args 条件 + * @param page 分页条件 + * @return / + */ + public KitPageResult searchRoleByArgs(SystemQueryRoleArgs args, Page page) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.injectQueryParams(args, wrapper); + IPage data = systemRoleMapper.selectPage(page, wrapper).convert(systemUserRoleService::convertToRoleVo); + return KitPageUtil.toPage(data); + } + + /** + * 查询用户权限信息? + * + * @param user 用户信息 + * @return 权限信息 + */ + public List buildUserRolePermissions(SystemUserVo user) { + Set permissions = new HashSet<>(); + // 如果是管理员直接返回 + if (user.getIsAdmin()) { + permissions.add("admin"); + return permissions.stream().map(SystemRoleCodeVo::new).collect(Collectors.toList()); + } +// List roles = systemUserRoleService.listRoleVoByUsersId(user.getId()); +// permissions = roles.stream().flatMap(role -> role.getMenus().stream()).map(SystemMenuVo::getPermission) +// .filter(StrUtil::isNotBlank).collect(Collectors.toSet()); +// return permissions.stream().map(SystemRoleCodeVo::new).collect(Collectors.toList()); + Set roleIds = systemUserRoleService.listUserRoleIdByUserId(user.getId()); + // 查询角色权限 + if (CollUtil.isNotEmpty(roleIds)) { + return systemRoleMenuService.listMenuPermissionByRoleIds(roleIds).stream().map(SystemRoleCodeVo::new).collect(Collectors.toList()); + } + // 没有包含任何角色,赋予默认权限 + permissions.add("IsAJoker"); + return permissions.stream().map(SystemRoleCodeVo::new).collect(Collectors.toList()); + } + + /** + * 根据角色id查询用户可访问的部门和菜单信息 + * + * @param id 角色id + * @return / + */ + public SystemRoleVo getRoleVoById(Long id) { + SystemRoleTb roleTb = systemRoleMapper.selectById(id); + if (roleTb == null) { + return null; + } + return systemUserRoleService.convertToRoleVo(roleTb); + } + + /** + * 查询当前用户角色级别 + * + * @return / + */ + public Dict getCurrentUserRoleLevel() { + return Dict.create().set("level", this.checkRoleLevels(null)); + } + + /** + * 构建查询条件 + * + * @param args / + * @param wrapper / + */ + private void injectQueryParams(SystemQueryRoleArgs args, LambdaQueryWrapper wrapper) { + if (args != null) { + wrapper.and(StrUtil.isNotBlank(args.getBlurry()), c -> c.like(SystemRoleTb::getName, args.getBlurry()).or() + .like(SystemRoleTb::getDescription, args.getBlurry())); + if (CollUtil.isNotEmpty(args.getCreateTime()) && args.getCreateTime().size() >= 2) { + wrapper.between(SystemRoleTb::getCreateTime, args.getCreateTime().get(0), args.getCreateTime().get(1)); + } + } + wrapper.orderByAsc(SystemRoleTb::getLevel); + } + + /** + * 查询角色名称是否存在 + * + * @param name 角色名称 + * @return / + */ + private boolean existRoleWithName(String name) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemRoleTb::getName, name); + return systemRoleMapper.exists(wrapper); + } + + /** + * 查询角色名称是否存在(排除本身) + * + * @param name 角色名称 + * @return / + */ + private boolean existRoleWithNameNeSelf(String name, Long roleId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemRoleTb::getName, name); + wrapper.eq(SystemRoleTb::getId, roleId); + return systemRoleMapper.exists(wrapper); + } + + /** + * 检查用户的角色级别 + * + * @return / + */ + private int checkRoleLevels(Integer level) { + Set levels = systemUserRoleService.listUserRoleLevelByUserId(KitSecurityHelper.getCurrentUserId()); + int min = Collections.min(levels); + if (level != null) { + KitValidUtil.isTrue(level < min, "权限不足, 你的角色级别:" + min + ", 低于操作的角色级别:" + level); + } + return min; + } + + /** + * 导出部门数据 + * + * @param response / + * @param args / + */ + public void exportRoleXlsx(HttpServletResponse response, SystemQueryRoleArgs args) { + List systemRoleVos = this.queryRoleByArgs(args, null); + List rowVos = new ArrayList<>(); + for (SystemRoleTb dataObject : systemRoleVos) { + SystemRoleExportRowVo rowVo = new SystemRoleExportRowVo(); + rowVo.setName(dataObject.getName()); + rowVo.setLevel(dataObject.getLevel()); + rowVo.setDescription(dataObject.getDescription()); + rowVo.setCreateTime(dataObject.getCreateTime()); + rowVos.add(rowVo); + } + KitExcelExporter.exportSimple(response, "部门数据", SystemRoleExportRowVo.class, rowVos); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserDeptService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserDeptService.java new file mode 100644 index 00000000..cd009afa --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserDeptService.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2026 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.system.dal.dataobject.SystemUserTb; +import cn.odboy.system.dal.mysql.SystemUserMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.Set; + +@Service +public class SystemUserDeptService { + + @Autowired + private SystemUserMapper systemUserMapper; + + /** + * 根据部门id统计用户数量 + */ + public long countUserByDeptIds(Set deptIds) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(SystemUserTb::getDeptId, deptIds); + return systemUserMapper.selectCount(wrapper); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserJobService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserJobService.java new file mode 100644 index 00000000..a42de3a8 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserJobService.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021-2026 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.dal.dataobject.SystemJobTb; +import cn.odboy.system.dal.dataobject.SystemUserJobTb; +import cn.odboy.system.dal.mysql.SystemUserJobMapper; +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 java.util.ArrayList; +import java.util.List; +import java.util.Set; + +@Service +public class SystemUserJobService { + + @Autowired + private SystemUserJobMapper systemUserJobMapper; + + /** + * 批量绑定用户岗位 + */ + @Transactional(rollbackFor = Exception.class) + public void batchInsertUserJob(Set jobs, Long userId) { + if (CollUtil.isNotEmpty(jobs)) { + List records = new ArrayList<>(); + for (SystemJobTb job : jobs) { + SystemUserJobTb record = new SystemUserJobTb(); + record.setUserId(userId); + record.setJobId(job.getId()); + records.add(record); + } + systemUserJobMapper.insert(records); + } + } + + /** + * 批量解绑用户岗位 + */ + @Transactional(rollbackFor = Exception.class) + public void batchDeleteUserJob(Set userIds) { + if (CollUtil.isNotEmpty(userIds)) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(SystemUserJobTb::getUserId, userIds); + systemUserJobMapper.delete(wrapper); + } + } + + /** + * 根据岗位ID统计用户数量 + * + * @param jobIds / + * @return / + */ + public Long countUserByJobIds(Set jobIds) { + if (CollUtil.isEmpty(jobIds)) { + return 0L; + } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(SystemUserJobTb::getJobId, jobIds); + return systemUserJobMapper.selectCount(wrapper); + } + + /** + * 根据用户id查询岗位 + * + * @param userId / + * @return / + */ + public Set listUserJobByUserId(Long userId) { + return systemUserJobMapper.listUserJobByUserId(userId); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserRoleService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserRoleService.java new file mode 100644 index 00000000..af81a009 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserRoleService.java @@ -0,0 +1,196 @@ +/* + * Copyright 2021-2026 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.framework.exception.BadRequestException; +import cn.odboy.system.dal.dataobject.SystemDeptTb; +import cn.odboy.system.dal.dataobject.SystemMenuTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import cn.odboy.system.dal.dataobject.SystemUserRoleTb; +import cn.odboy.system.dal.model.response.SystemRoleVo; +import cn.odboy.system.dal.model.response.SystemUserVo; +import cn.odboy.system.dal.mysql.SystemRoleMapper; +import cn.odboy.system.dal.mysql.SystemUserRoleMapper; +import cn.odboy.system.framework.permission.core.KitSecurityHelper; +import cn.odboy.util.KitBeanUtil; +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 java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class SystemUserRoleService { + + @Autowired + private SystemRoleMapper systemRoleMapper; + @Autowired + private SystemUserRoleMapper systemUserRoleMapper; + @Autowired + private SystemRoleMenuService systemRoleMenuService; + @Autowired + private SystemRoleDeptService systemRoleDeptService; + + /** + * 批量绑定用户角色 + */ + @Transactional(rollbackFor = Exception.class) + public void batchInsertUserRole(Set roles, Long userId) { + if (CollUtil.isNotEmpty(roles)) { + List records = new ArrayList<>(); + for (SystemRoleTb role : roles) { + SystemUserRoleTb record = new SystemUserRoleTb(); + record.setUserId(userId); + record.setRoleId(role.getId()); + records.add(record); + } + systemUserRoleMapper.insert(records); + } + } + + /** + * 批量解绑用户角色 + */ + @Transactional(rollbackFor = Exception.class) + public void batchDeleteUserRole(Set userIds) { + if (CollUtil.isNotEmpty(userIds)) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(SystemUserRoleTb::getUserId, userIds); + systemUserRoleMapper.delete(wrapper); + } + } + + /** + * 转换为SystemRoleVo并关联查询菜单和部门信息 + * + * @param role 角色基本信息 + * @return 包含关联信息的SystemRoleVo + */ + public SystemRoleVo convertToRoleVo(SystemRoleTb role) { + if (role == null) { + return null; + } + SystemRoleVo roleVo = KitBeanUtil.copyToClass(role, SystemRoleVo.class); + // 查询关联的菜单信息 + List menus = systemRoleMenuService.listMenuByRoleIds(Collections.singleton(role.getId()), true); + roleVo.setMenus(new HashSet<>(menus)); + // 查询关联的部门信息 + Set depts = systemRoleDeptService.listUserDeptByRoleId(role.getId()); + roleVo.setDepts(depts); + return roleVo; + } + + /** + * 根据用户ID查询 + * + * @param userId 用户ID + * @return / + */ + public List listRoleVoByUsersId(Long userId) { + Set roles = this.listUserRoleByUserId(userId); + return roles.stream().map(this::convertToRoleVo).collect(Collectors.toList()); + } + + /** + * 根据角色查询角色级别 + * + * @param roles / + * @return / + */ + public Integer getDeptLevelByRoles(Set roles) { + if (CollUtil.isEmpty(roles)) { + // 最小权限 + return Integer.MAX_VALUE; + } + List roleIds = roles.stream().map(SystemRoleTb::getId).filter(Objects::nonNull).distinct().collect(Collectors.toList()); + if (CollUtil.isEmpty(roleIds)) { + // 最小权限 + return Integer.MAX_VALUE; + } + List roleLevels = systemRoleMapper.listRoleLevelByRoleIds(roleIds); + if (CollUtil.isEmpty(roleLevels)) { + // 最小权限 + return Integer.MAX_VALUE; + } + return Collections.min(roleLevels); + } + + /** + * 如果当前用户的角色级别低于创建用户的角色级别,则抛出权限不足的错误 + * + * @param args / + */ + public void checkLevel(SystemUserVo args) { + Set roleLevels = systemUserRoleMapper.listUserRoleLevelByUserId(KitSecurityHelper.getCurrentUserId()); + Integer currentLevel = Collections.min(roleLevels); + Integer optLevel = this.getDeptLevelByRoles(args.getRoles()); + if (currentLevel > optLevel) { + throw new BadRequestException("角色权限不足"); + } + } + + /** + * 根据用户id查询角色 + * + * @param userId / + * @return / + */ + public Set listUserRoleByUserId(Long userId) { + return systemUserRoleMapper.listUserRoleByUserId(userId); + } + + /** + * 根据用户id查询角色级别集合 + * + * @param userId / + * @return / + */ + public Set listUserRoleLevelByUserId(Long userId) { + return systemUserRoleMapper.listUserRoleLevelByUserId(userId); + } + + /** + * 根据用户id查询角色id集合 + * + * @param userId / + * @return / + */ + public Set listUserRoleIdByUserId(Long userId) { + return systemUserRoleMapper.listUserRoleIdByUserId(userId); + } + + /** + * 根据角色id集合判断是否存在关联用户 + * + * @param ids 角色id集合 + * @return / + */ + public boolean existUserWithRoleIds(Set ids) { + if (CollUtil.isEmpty(ids)) { + return false; + } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(SystemUserRoleTb::getRoleId, ids); + return systemUserRoleMapper.exists(wrapper); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserService.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserService.java new file mode 100644 index 00000000..f6e24670 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/SystemUserService.java @@ -0,0 +1,527 @@ +/* + * Copyright 2021-2026 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.util.StrUtil; +import cn.odboy.base.KitPageResult; +import cn.odboy.framework.properties.AppProperties; +import cn.odboy.framework.server.core.KitFileLocalUploadHelper; +import cn.odboy.system.constant.SystemCaptchaBizEnum; +import cn.odboy.system.constant.SystemZhConst; +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.dal.model.export.SystemUserExportRowVo; +import cn.odboy.system.dal.model.request.SystemQueryUserArgs; +import cn.odboy.system.dal.model.request.SystemUpdateUserPasswordArgs; +import cn.odboy.system.dal.model.response.SystemUserVo; +import cn.odboy.system.dal.mysql.SystemUserMapper; +import cn.odboy.system.dal.redis.SystemUserInfoDAO; +import cn.odboy.system.dal.redis.SystemUserOnlineInfoDAO; +import cn.odboy.system.framework.permission.core.KitSecurityHelper; +import cn.odboy.util.KitBeanUtil; +import cn.odboy.util.KitFileUtil; +import cn.odboy.util.KitPageUtil; +import cn.odboy.util.KitRsaEncryptUtil; +import cn.odboy.util.KitValidUtil; +import cn.odboy.util.xlsx.KitExcelExporter; +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.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.ObjectUtils; +import org.springframework.web.multipart.MultipartFile; +import jakarta.servlet.http.HttpServletResponse; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class SystemUserService { + + @Autowired + private SystemUserMapper systemUserMapper; + @Autowired + private SystemDeptService systemDeptService; + @Autowired + private SystemUserJobService systemUserJobService; + @Autowired + private SystemUserRoleService systemUserRoleService; + @Autowired + private SystemEmailService systemEmailService; + @Autowired + private SystemDataService systemDataService; + @Autowired + private SystemUserInfoDAO systemUserInfoDAO; + @Autowired + private SystemUserOnlineInfoDAO systemUserOnlineInfoDAO; + @Autowired + private KitFileLocalUploadHelper fileUploadPathHelper; + @Autowired + private AppProperties properties; + @Autowired + private PasswordEncoder passwordEncoder; + + /** + * 新增用户 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void saveUser(SystemUserVo args) { + systemUserRoleService.checkLevel(args); + SystemUserTb record = KitBeanUtil.copyToClass(args, SystemUserTb.class); + // 默认密码 123456 + record.setPassword(passwordEncoder.encode("123456")); + record.setDeptId(args.getDept().getId()); + KitValidUtil.isTrue(this.existUserWithUsername(record.getUsername()), "用户名已存在"); + KitValidUtil.isTrue(this.existUserWithEmail(record.getEmail()), "邮箱已存在"); + KitValidUtil.isTrue(this.existUserWithPhone(record.getPhone()), "手机号已存在"); + // 保存用户 + systemUserMapper.insert(record); + // 保存用户岗位 + systemUserJobService.batchInsertUserJob(args.getJobs(), record.getId()); + // 保存用户角色 + systemUserRoleService.batchInsertUserRole(args.getRoles(), record.getId()); + } + + /** + * 编辑用户 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void updateUserById(SystemUserVo args) { + systemUserRoleService.checkLevel(args); + SystemUserTb user = systemUserMapper.selectById(args.getId()); + KitValidUtil.isTrue(this.existUserWithUsernameNeSelf(args.getUsername(), user.getId()), "用户名已存在"); + KitValidUtil.isTrue(this.existUserWithEmailNeSelf(args.getEmail(), user.getId()), "邮箱已存在"); + KitValidUtil.isTrue(this.existUserWithPhoneNeSelf(args.getPhone(), user.getId()), "手机号已存在"); + // 如果用户被禁用, 则清除用户登录信息 + 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.setPhone(args.getPhone()); + user.setNickName(args.getNickName()); + user.setGender(args.getGender()); + systemUserMapper.updateById(user); + // 清除用户登录缓存 + systemUserInfoDAO.deleteUserLoginInfoByUserName(user.getUsername()); + // 更新用户岗位 + systemUserJobService.batchDeleteUserJob(Collections.singleton(args.getId())); + systemUserJobService.batchInsertUserJob(args.getJobs(), args.getId()); + // 更新用户角色 + systemUserRoleService.batchDeleteUserRole(Collections.singleton(args.getId())); + systemUserRoleService.batchInsertUserRole(args.getRoles(), args.getId()); + } + + /** + * 用户自助修改资料 + * + * @param args / + */ + @Transactional(rollbackFor = Exception.class) + public void updateUserCenterInfoById(SystemUserTb args) { + KitValidUtil.isTrue(!args.getId().equals(KitSecurityHelper.getCurrentUserId()), "不能修改他人资料"); + SystemUserTb user = systemUserMapper.selectById(args.getId()); + KitValidUtil.isTrue(this.existUserWithPhoneNeSelf(args.getPhone(), user.getId()), "手机号已存在"); + user.setNickName(args.getNickName()); + user.setPhone(args.getPhone()); + user.setGender(args.getGender()); + systemUserMapper.updateById(user); + systemUserInfoDAO.deleteUserLoginInfoByUserName(user.getUsername()); + } + + /** + * 删除用户 + * + * @param ids / + */ + @Transactional(rollbackFor = Exception.class) + public void deleteUserByIds(Set ids) { + Long currentUserId = KitSecurityHelper.getCurrentUserId(); + // 校验权限 + for (Long id : ids) { + SystemUserTb systemUserTb = systemUserMapper.selectById(id); + Integer currentLevel = Collections.min(systemUserRoleService.listUserRoleLevelByUserId(currentUserId)); + Integer optLevel = Collections.min(systemUserRoleService.listUserRoleLevelByUserId(id)); + KitValidUtil.isTrue(currentLevel > optLevel, "角色权限不足, 不能删除:" + systemUserTb.getUsername()); + } + // 清理缓存 + List usernameList = systemUserMapper.listUsernameByIds(ids); + for (String username : usernameList) { + systemUserInfoDAO.deleteUserLoginInfoByUserName(username); + } + // 删除用户 + systemUserMapper.deleteByIds(ids); + // 删除用户岗位 + systemUserJobService.batchDeleteUserJob(ids); + // 删除用户角色 + systemUserRoleService.batchDeleteUserRole(ids); + } + + /** + * 修改密码 + * + * @param username 用户名 + * @param args 参数 + */ + @Transactional(rollbackFor = Exception.class) + public void updateUserPasswordByUsername(String username, SystemUpdateUserPasswordArgs args) throws Exception { + String oldPass = KitRsaEncryptUtil.decryptByPrivateKey(properties.getRsa().getPrivateKey(), args.getOldPass()); + String newPass = KitRsaEncryptUtil.decryptByPrivateKey(properties.getRsa().getPrivateKey(), args.getNewPass()); + SystemUserTb user = this.getUserByUsername(username); + KitValidUtil.isTrue(!passwordEncoder.matches(oldPass, user.getPassword()), "修改失败,旧密码错误"); + KitValidUtil.isTrue(passwordEncoder.matches(newPass, user.getPassword()), "新密码不能与旧密码相同"); + String encryptPassword = passwordEncoder.encode(newPass); + systemUserMapper.updateUserPasswordByUsername(username, encryptPassword); + systemUserInfoDAO.deleteUserLoginInfoByUserName(username); + } + + /** + * 重置密码 + * + * @param ids 用户id + */ + @Transactional(rollbackFor = Exception.class) + public void resetUserPasswordByIds(Set ids) { + String defaultPwd = passwordEncoder.encode("123456"); + List users = systemUserMapper.listUsernameByIds(ids); + // 清除缓存 + for (String username : users) { + // 清除缓存 + systemUserInfoDAO.deleteUserLoginInfoByUserName(username); + // 强制退出 + systemUserOnlineInfoDAO.kickOutByUsername(username); + } + // 重置密码 + systemUserMapper.updateUserPasswordByUserIds(defaultPwd, ids); + } + + /** + * 修改头像 + * + * @param multipartFile 文件 + * @return / + */ + @Transactional(rollbackFor = Exception.class) + public Map updateUserAvatar(MultipartFile multipartFile) { + SystemUserTb user = this.getUserByUsername(KitSecurityHelper.getCurrentUsername()); + String username = user.getUsername(); + KitValidUtil.isBlank(username, "异常用户数据, 请联系管理员处理"); + // 文件大小验证 + KitFileUtil.checkSize(fileUploadPathHelper.getAvatarMaxSize(), multipartFile.getSize()); + // 验证文件上传的格式 + String image = "gif jpg png jpeg"; + String fileType = KitFileUtil.getSuffix(multipartFile.getOriginalFilename()); + KitValidUtil.isTrue(fileType != null && !image.contains(fileType), "文件格式错误, 仅支持 " + image + " 格式"); + String oldPath = user.getAvatarPath(); + File file = KitFileUtil.upload(multipartFile, fileUploadPathHelper.getPath()); + user.setAvatarPath(Objects.requireNonNull(file).getPath()); + user.setAvatarName(file.getName()); + systemUserMapper.updateById(user); + if (StrUtil.isNotBlank(oldPath)) { + KitFileUtil.del(oldPath); + } + systemUserInfoDAO.deleteUserLoginInfoByUserName(username); + return new HashMap<>(1) {{ + put("avatar", file.getName()); + }}; + } + + /** + * 修改邮箱 + * + * @param username 用户名 + * @param args 参数 + * @param code 验证码 + */ + @Transactional(rollbackFor = Exception.class) + public void updateUserEmailByUsername(String username, SystemUserTb args, String code) throws Exception { + String password = KitRsaEncryptUtil.decryptByPrivateKey(properties.getRsa().getPrivateKey(), args.getPassword()); + SystemUserTb user = this.getUserByUsername(username); + KitValidUtil.isTrue(!passwordEncoder.matches(password, user.getPassword()), "密码错误"); + systemEmailService.checkEmailCaptchaV1(SystemCaptchaBizEnum.EMAIL_RESET_EMAIL_CODE, args.getEmail(), code); + systemUserMapper.updateUserEmailByUsername(username, args.getEmail()); + systemUserInfoDAO.deleteUserLoginInfoByUserName(username); + } + + /** + * 查询全部 + * + * @param args 条件 + * @param page 分页参数 + * @return / + */ + public KitPageResult searchUserByArgs(SystemQueryUserArgs args, Page page) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.injectQueryParams(args, wrapper); + IPage userPage = systemUserMapper.selectPage(page, wrapper); + List users = userPage.getRecords(); + // 转换为SystemUserVo并关联查询 + List userVos = users.stream().map(m -> this.convertToUserVo(m, false)).collect(Collectors.toList()); + return KitPageUtil.toPage(userVos, userPage.getTotal()); + } + + /** + * 复杂条件统计用户数量 + * + * @param args / + * @return / + */ + public long countUserByArgs(SystemQueryUserArgs args) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.injectQueryParams(args, wrapper); + return systemUserMapper.selectCount(wrapper); + } + + /** + * 根据用户名查询启用的用户信息 + * + * @param username 用户名 + * @return / + */ + public SystemUserTb getUserByUsername(String username) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemUserTb::getUsername, username); + wrapper.eq(SystemUserTb::getEnabled, 1); + return systemUserMapper.selectOne(wrapper); + } + + /** + * 根据用户名查询用户的部门、岗位和角色信息,用户信息中有密码数据 + * + * @param username 用户名 + * @return / + */ + public SystemUserVo getUserVoWithPasswordByUsername(String username) { + return this.convertToUserVo(this.getUserByUsername(username), true); + } + + /** + * 根据用户名查询用户的部门、岗位和角色信息,用户信息中无密码数据 + * + * @param username 用户名 + * @return / + */ + public SystemUserVo getUserVoByUsername(String username, boolean includePassword) { + return this.convertToUserVo(this.getUserByUsername(username), includePassword); + } + + /** + * 聚合查询 + */ + public KitPageResult aggregationSearchUserByArgs(Page page, SystemQueryUserArgs args, String currentUsername) { + if (!ObjectUtils.isEmpty(args.getDeptId())) { + args.getDeptIds().add(args.getDeptId()); + // 先查找是否存在子节点 + List data = systemDeptService.listDeptByPid(args.getDeptId()); + Map> deptPidMap = systemDeptService.listPidNonNull().stream().collect(Collectors.groupingBy(SystemDeptTb::getPid)); + // 然后把子节点的ID都加入到集合中 + args.getDeptIds().addAll(systemDeptService.queryChildDeptIdByDeptIds(data, deptPidMap)); + } + // 数据权限 + List dataScopes = systemDataService.queryDeptIdByArgs(this.getUserVoByUsername(currentUsername, false)); + // args.getDeptIds() 不为空并且数据权限不为空则取交集 + if (!CollUtil.isEmpty(args.getDeptIds()) && !CollUtil.isEmpty(dataScopes)) { + // 取交集 + args.getDeptIds().retainAll(dataScopes); + if (!CollUtil.isEmpty(args.getDeptIds())) { + return this.searchUserByArgs(args, page); + } + } else { + // 否则取并集 + args.getDeptIds().addAll(dataScopes); + return this.searchUserByArgs(args, page); + } + return KitPageUtil.emptyData(); + } + + /** + * 导出 + */ + public void exportUserXlsx(HttpServletResponse response, SystemQueryUserArgs args) { + long totalCount = this.countUserByArgs(args); + KitExcelExporter.exportByPage(response, "用户数据", SystemUserExportRowVo.class, totalCount, + (long pageNum, long pageSize) -> { + KitPageResult pageResult = this.searchUserByArgs(args, new Page<>(pageNum, pageSize)); + List rowVos = new ArrayList<>(); + for (SystemUserVo dataObject : pageResult.getContent()) { + SystemUserExportRowVo rowVo = new SystemUserExportRowVo(); + rowVo.setUsername(dataObject.getUsername()); + rowVo.setRoles( + dataObject.getRoles().stream().map(SystemRoleTb::getName).collect(Collectors.joining(","))); + rowVo.setDept(dataObject.getDept().getName()); + rowVo.setJobs( + dataObject.getJobs().stream().map(SystemJobTb::getName).collect(Collectors.joining(","))); + rowVo.setEmail(dataObject.getEmail()); + rowVo.setStatus(dataObject.getEnabled() ? SystemZhConst.ENABLE_STR : SystemZhConst.DISABLE_STR); + rowVo.setMobile(dataObject.getPhone()); + rowVo.setUpdatePwdTime(dataObject.getPwdResetTime()); + rowVo.setCreateTime(dataObject.getCreateTime()); + rowVos.add(rowVo); + } + return rowVos; + } + ); + } + + /** + * 查询手机号是否存在 + * + * @param phone 手机号 + * @return / + */ + private boolean existUserWithPhone(String phone) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemUserTb::getPhone, phone); + wrapper.eq(SystemUserTb::getEnabled, 1); + return systemUserMapper.exists(wrapper); + } + + /** + * 查询邮箱是否存在 + * + * @param email 邮箱 + * @return / + */ + private boolean existUserWithEmail(String email) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemUserTb::getEmail, email); + wrapper.eq(SystemUserTb::getEnabled, 1); + return systemUserMapper.exists(wrapper); + } + + /** + * 查询用户名是否存在 + * + * @param username 用户名 + * @return / + */ + private boolean existUserWithUsername(String username) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemUserTb::getUsername, username); + wrapper.eq(SystemUserTb::getEnabled, 1); + return systemUserMapper.exists(wrapper); + } + + /** + * 查询手机号是否存在(排除指定用户) + * + * @param phone 手机号 + * @param currentUserId 指定用户id + * @return / + */ + private boolean existUserWithPhoneNeSelf(String phone, Long currentUserId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemUserTb::getPhone, phone); + wrapper.ne(SystemUserTb::getId, currentUserId); + return systemUserMapper.exists(wrapper); + } + + /** + * 查询邮箱是否存在(排除指定用户) + * + * @param email 邮箱 + * @param currentUserId 指定用户id + * @return / + */ + private boolean existUserWithEmailNeSelf(String email, Long currentUserId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemUserTb::getEmail, email); + wrapper.ne(SystemUserTb::getId, currentUserId); + return systemUserMapper.exists(wrapper); + } + + /** + * 查询用户名是否存在(排除指定用户) + * + * @param username 用户名 + * @param currentUserId 指定用户id + * @return / + */ + private boolean existUserWithUsernameNeSelf(String username, Long currentUserId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemUserTb::getUsername, username); + wrapper.ne(SystemUserTb::getId, currentUserId); + return systemUserMapper.exists(wrapper); + } + + /** + * 构建查询条件 + * + * @param args / + * @param wrapper / + */ + private void injectQueryParams(SystemQueryUserArgs args, LambdaQueryWrapper wrapper) { + if (args != null) { + wrapper.eq(args.getId() != null, SystemUserTb::getId, args.getId()); + wrapper.eq(args.getEnabled() != null, SystemUserTb::getEnabled, args.getEnabled()); + wrapper.in(CollUtil.isNotEmpty(args.getDeptIds()), SystemUserTb::getDeptId, args.getDeptIds()); + if (StrUtil.isNotBlank(args.getBlurry())) { + wrapper.and(w -> w.like(SystemUserTb::getUsername, args.getBlurry()).or() + .like(SystemUserTb::getNickName, args.getBlurry()).or().like(SystemUserTb::getEmail, args.getBlurry())); + } + if (CollUtil.isNotEmpty(args.getCreateTime()) && args.getCreateTime().size() >= 2) { + wrapper.between(SystemUserTb::getCreateTime, args.getCreateTime().get(0), args.getCreateTime().get(1)); + } + } + wrapper.orderByDesc(SystemUserTb::getCreateTime); + } + + /** + * 转换为SystemUserVo并关联查询部门、角色、岗位信息 + * + * @param user 用户基本信息 + * @param includePassword 是否包含用户密码 + * @return 包含关联信息的SystemUserVo + */ + private SystemUserVo convertToUserVo(SystemUserTb user, boolean includePassword) { + if (user == null) { + return null; + } + if (!includePassword) { + // 查询用户时,清空密码 + user.setPassword(null); + } + SystemUserVo userVo = KitBeanUtil.copyToClass(user, SystemUserVo.class); + // 查询关联的部门信息 + if (user.getDeptId() != null) { + userVo.setDept(systemDeptService.getDeptById(user.getDeptId())); + } + // 查询关联的岗位信息 + userVo.setJobs(systemUserJobService.listUserJobByUserId(user.getId())); + // 查询关联的角色信息 + userVo.setRoles(systemUserRoleService.listUserRoleByUserId(user.getId())); + return userVo; + } + +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/impl/SystemMinioStorageServiceImpl.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/impl/SystemMinioStorageServiceImpl.java new file mode 100644 index 00000000..ef0296ca --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/service/impl/SystemMinioStorageServiceImpl.java @@ -0,0 +1,172 @@ +/* + * Copyright 2021-2026 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.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.base.KitPageResult; +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.KitFileLocalUploadHelper; +import cn.odboy.system.dal.dataobject.SystemOssStorageTb; +import cn.odboy.system.dal.model.export.SystemOssStorageExportRowVo; +import cn.odboy.system.dal.model.request.SystemQueryStorageArgs; +import cn.odboy.system.dal.model.response.SystemOssStorageVo; +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.KitBeanUtil; +import cn.odboy.util.KitDateUtil; +import cn.odboy.util.KitFileUtil; +import cn.odboy.util.KitPageUtil; +import cn.odboy.util.KitValidUtil; +import cn.odboy.util.xlsx.KitExcelExporter; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +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 jakarta.servlet.http.HttpServletResponse; +import java.io.File; +import java.util.List; + +/** + *

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

+ * + * @author codegen + * @since 2025-07-15 + */ +@Service +public class SystemMinioStorageServiceImpl extends ServiceImpl + implements SystemOssStorageService { + + @Autowired + private MinioRepository minioRepository; + @Autowired + private AppProperties properties; + @Autowired + private KitFileLocalUploadHelper fileLocalUploadHelper; + @Autowired + private SystemOssStorageMapper systemOssStorageMapper; + + @Override + public KitPageResult searchOssStorage( + SystemQueryStorageArgs args, + Page page + ) { + IPage ossStorageTbs = this.selectOssStorageByArgs(args, page); + for (SystemOssStorageVo storageVo : ossStorageTbs.getRecords()) { + storageVo.setFileSizeDesc(KitFileUtil.getSize(storageVo.getFileSize())); + } + return KitPageUtil.toPage(ossStorageTbs); + } + + private IPage selectOssStorageByArgs(SystemQueryStorageArgs args, Page page) { + KitValidUtil.isNull(args); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.and( + StrUtil.isNotBlank(args.getBlurry()), + c -> c.like(SystemOssStorageTb::getFileName, args.getBlurry()).or() + .like(SystemOssStorageTb::getFilePrefix, args.getBlurry()).or() + .like(SystemOssStorageTb::getFileMime, args.getBlurry()).or() + .like(SystemOssStorageTb::getFileMd5, args.getBlurry()) + ); + if (CollUtil.isNotEmpty(args.getCreateTime()) && args.getCreateTime().size() >= 2) { + wrapper.between(SystemOssStorageTb::getUpdateTime, args.getCreateTime().get(0), + args.getCreateTime().get(1) + ); + } + wrapper.orderByAsc(SystemOssStorageTb::getId); + return systemOssStorageMapper.selectPage(page, wrapper) + .convert(i -> KitBeanUtil.copyToClass(i, SystemOssStorageVo.class)); + } + + @Override + public List queryOssStorage(SystemQueryStorageArgs args) { + // 防止刷数据 + Page page = new Page<>(1, 500); + return searchOssStorage(args, page).getContent(); + } + + @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(); + KitFileUtil.checkSize(ossConfig.getMaxSize(), fileSize); + // 按天分组 + String nowDateStr = KitDateUtil.getNowDateStr(); + // 上传到本地临时目录 + File tempFile = KitFileUtil.upload(file, fileLocalUploadHelper.getPath() + nowDateStr + File.separator); + if (tempFile == null) { + throw new BadRequestException("上传失败"); + } + // 校验文件md5, 看是否已存在云端(不确定, 可能云端已经删除, 但是正常来说云端是不允许私自删除的, 所以这里忽略云端不存在的情况) + String md5 = KitFileUtil.getMd5(tempFile); + SystemOssStorageTb systemOssStorageTb = this.getOssStorageByMd5(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 deleteFileByIds(Long[] ids) { + for (Long id : ids) { + SystemOssStorageTb storage = getById(id); + minioRepository.removeBucketFile(storage.getObjectName()); + removeById(storage); + } + } + + @Override + public void exportOssStorageXlsx(HttpServletResponse response, SystemQueryStorageArgs args) { + List systemOssStorageVos = this.queryOssStorage(args); + // KitXlsxExportUtil.exportFile(response, "OSS文件上传记录数据", systemOssStorageVos, SystemOssStorageExportRowVo.class, + // (dataObject) -> CollUtil.newArrayList(KitBeanUtil.copyProperties(dataObject, SystemOssStorageExportRowVo.class))); + List rowVos = + KitBeanUtil.copyToList(systemOssStorageVos, SystemOssStorageExportRowVo.class); + KitExcelExporter.exportSimple(response, "OSS文件上传记录数据", SystemOssStorageExportRowVo.class, rowVos); + } + + private SystemOssStorageTb getOssStorageByMd5(String md5) { + return lambdaQuery().eq(SystemOssStorageTb::getFileMd5, md5).one(); + } +} diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/util/KitSqlUtil.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/util/KitSqlUtil.java new file mode 100644 index 00000000..7ab4e498 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/util/KitSqlUtil.java @@ -0,0 +1,189 @@ +/* + * Copyright 2021-2026 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.KitCloseUtil; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.google.common.collect.Lists; +import com.zaxxer.hikari.HikariDataSource; +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; +import javax.sql.DataSource; +import lombok.extern.slf4j.Slf4j; + +/** + * 不建议在生产环境使用 + */ +@Slf4j +public final class KitSqlUtil { + + /** + * 获取数据源 + * + * @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("获取驱动类名失败,jdbcUrl=" + jdbcUrl); + } + if (StringUtils.isEmpty(className)) { + DataTypeEnum dataTypeEnum = DataTypeEnum.urlOf(jdbcUrl); + if (null == dataTypeEnum) { + throw new BadRequestException("不支持的数据库类型,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 = dataSource.getConnection(); + } + } catch (Exception e) { + log.error("创建连接失败, jdbcUrl={}", jdbcUrl); + throw new BadRequestException("创建连接失败, jdbcUrl=" + jdbcUrl); + } finally { + KitCloseUtil.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 { + KitCloseUtil.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-springboot4/cutejava-module-system/src/main/resources/META-INF/resources/druid/index.html b/cutejava-springboot4/cutejava-module-system/src/main/resources/META-INF/resources/druid/index.html new file mode 100644 index 00000000..404b3299 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/resources/META-INF/resources/druid/index.html @@ -0,0 +1,13 @@ + + + + + + + + 版本使用警告 + + +

SpringBoot4不支持Druid,所以也就没有Druid SQL监控功能。默认使用HikariCP连接池

+ + diff --git a/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemDictDetailMapper.xml b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemDictDetailMapper.xml new file mode 100644 index 00000000..c8985ddd --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemDictDetailMapper.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemRoleDeptMapper.xml b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemRoleDeptMapper.xml new file mode 100644 index 00000000..96d2714c --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemRoleDeptMapper.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemRoleMapper.xml b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemRoleMapper.xml new file mode 100644 index 00000000..486a3730 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemRoleMapper.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemRoleMenuMapper.xml b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemRoleMenuMapper.xml new file mode 100644 index 00000000..c19292ae --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemRoleMenuMapper.xml @@ -0,0 +1,40 @@ + + + + + + + diff --git a/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemUserJobMapper.xml b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemUserJobMapper.xml new file mode 100644 index 00000000..5ffdd7f1 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemUserJobMapper.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemUserMapper.xml b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemUserMapper.xml new file mode 100644 index 00000000..87c7aad5 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemUserMapper.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemUserRoleMapper.xml b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemUserRoleMapper.xml new file mode 100644 index 00000000..19b83a1f --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/resources/mapper/SystemUserRoleMapper.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/cutejava-springboot4/cutejava-module-system/src/main/resources/template/SystemEmailCaptchaTemplate.ftl b/cutejava-springboot4/cutejava-module-system/src/main/resources/template/SystemEmailCaptchaTemplate.ftl new file mode 100644 index 00000000..78b79672 --- /dev/null +++ b/cutejava-springboot4/cutejava-module-system/src/main/resources/template/SystemEmailCaptchaTemplate.ftl @@ -0,0 +1,49 @@ + + + + + + + +
+
+

尊敬的用户, 您好:

+

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

+

${code}

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

任务信息:

+ + + + + + + + + + + + + + + + + +
任务名称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-springboot4/cutejava-starter/pom.xml b/cutejava-springboot4/cutejava-starter/pom.xml new file mode 100644 index 00000000..309d4a67 --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/pom.xml @@ -0,0 +1,41 @@ + + + + cutejava-springboot4 + cn.odboy + 1.5.2 + + 4.0.0 + + cutejava-starter + 启动模块 + + + + cn.odboy + cutejava-module-system + 1.5.2 + + + + + + + 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-springboot4/cutejava-starter/src/main/java/cn/odboy/AppRun.java b/cutejava-springboot4/cutejava-starter/src/main/java/cn/odboy/AppRun.java new file mode 100644 index 00000000..17cfe0ef --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/main/java/cn/odboy/AppRun.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2026 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 java.net.UnknownHostException; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; + +@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-springboot4/cutejava-starter/src/main/java/cn/odboy/GenCode.java b/cutejava-springboot4/cutejava-starter/src/main/java/cn/odboy/GenCode.java new file mode 100644 index 00000000..c3cb27a9 --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/main/java/cn/odboy/GenCode.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2026 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.KitMpCmdGenUtil; +import java.util.List; + +/** + * 代码生成入口 + */ +public class GenCode { + + private static final String ADDR = "127.0.0.1"; + private static final Integer PORT = 3306; + 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) { + KitMpCmdGenUtil generator = new KitMpCmdGenUtil(); + generator.setDatabaseUrl(URL); + generator.setDriverClassName("com.mysql.cj.jdbc.Driver"); + generator.setDatabaseUsername(DATABASE_USER); + generator.setDatabasePassword(DATABASE_PWD); + genTaskCode(generator); + } + + private static void genTaskCode(KitMpCmdGenUtil generator) { + generator.gen("task", "", + List.of("task_instance_info", "task_instance_detail", "task_template_info", "task_instance_step_detail")); + } +} diff --git a/cutejava-springboot4/cutejava-starter/src/main/java/cn/odboy/HealthCheckPointer.java b/cutejava-springboot4/cutejava-starter/src/main/java/cn/odboy/HealthCheckPointer.java new file mode 100644 index 00000000..99ef451d --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/main/java/cn/odboy/HealthCheckPointer.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2026 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.KitHealthCheckPointService; +import cn.odboy.system.dal.mysql.SystemDictMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +@Component +public class HealthCheckPointer implements KitHealthCheckPointService { + + @Autowired + private SystemDictMapper systemDictMapper; + + /** + * 系统准备就绪 + */ + @Override + public ResponseEntity doReadiness() { + return ResponseEntity.ok(null); + } + + /** + * 系统是否正常 + */ + @Override + public ResponseEntity doLiveness() { + return ResponseEntity.ok(systemDictMapper.selectById(1)); + } +} diff --git a/cutejava-springboot4/cutejava-starter/src/main/resources/application-dev.yml b/cutejava-springboot4/cutejava-starter/src/main/resources/application-dev.yml new file mode 100644 index 00000000..42c4abee --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/main/resources/application-dev.yml @@ -0,0 +1,156 @@ +server: + port: 8000 + http2: + # 启用 HTTP/2 支持,提升传输效率 + enabled: true + 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: + 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: + database: ${REDIS_DB:1} + host: ${REDIS_HOST:127.0.0.1} + port: ${REDIS_PORT:6379} + password: ${REDIS_PWD:kd123456} + # 连接超时时间 + timeout: 5000 + repositories: + # 是否启用Redis存储(关闭防止出现 Multiple Spring Data modules found, entering strict repository configuration mode) + enabled: false + datasource: + driverClassName: com.p6spy.engine.spy.P6SpyDriver + url: jdbc:p6spy:mysql://127.0.0.1:3306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true + username: root + password: kd123456 + hikari: + connection-timeout: 60000 + maximum-pool-size: 10 + minimum-idle: 2 + idle-timeout: 30000 +# 集成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: MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCD3vlcA4vXODkQ5xJIY8hzHDHkPFy88PasFFBrT+jUdMXblyexT25Id/M/6qiGQMbvqQOT7UUD9y5B1rgIMCYrZ9ZarsqCCUp3TyFGQHKMu5Gxe7lZUCkAAbXki00zQKtcjs/jto5qf7pg9TQ2LX7hZhCfDF9Fh1RoqBjkbmBvdL6HHpU7WI3Uq5EwEnvLkUyHVY0k4z1tBvdfWm9onvd2tOnLXAkcC98/zSIbb6xKHQV9w7EKhE0R98JSxTZtg0fp0bqZcgO+jpdlK2ynBR9KK1+A4RHbTu68EIJXIWVqz7ZezT3LICgk2NpaZ4I98Du4m8mtdR+39IzLwk0hcFjjS+XCTh3gUTo4o4b1afShGWh9Epns0a+lTEQDnM5bqDYuwcpRRemA5+7n0zpkfG+IYy3bpbEmKr+J9UokTfJWDHQxUgYQVOsb6yk4JWwNgxIedWw8RR0vqDkVXZgvKo0eWMGpSrDI4R9SKenuE9HGsskMzJfoZbLvLjwOODTDKHaFeuj/KBWtnqNxJ3sxQB3aVi55QorwF7Ls7Mhw4noa94d0vMauyltuScO5GxtwidHlsPqXtgDi+mcuxuI4fbBsolZc2fFHA3kq5ZKnAcMeEBEHx/jBat7BQog7FwUj94nqR7oPnYkw4qGIZ5kqf7b+CIfCs8aRNER7js6x7pS9MwIDAQABAoICAGwcAJJO1fY7QY154qM2Kr8z/jL+yYicg53PiZ5whJCoqwy7fu7+D4uZyvI7jq2BrZYNGcHMYyg9HM8dGQKddq1fE3yEj8DLnSDy3nSZoU9+9pJqvClcbkBzJouXKFM8UUAwVH56y4bD8ChM3eEMq1heNIIvwmtd5vVl2gJr+uNk8k4T5IQitSQFA6JFhPf+xp6spGn5YgJgE604lt1OddH7vy/Y5B76HKxRoASeKMEZycaxCBN3Izf3xnfU9ioR1oL6TeA+eJNgkv19/DVxHZXQuRsPaXRB4baMUk7cMii+4OQXnvFIC6Epptk2wq/EozFVhqGNw9MrycHCKTmgwT1kOGOvl2PZwqC6+aOVLJxyIgvPUhRDIt297A0f86EwMXzQUzd0m/iICmVg0o2bdiyd5SxXv2JG+eB2e+orLeF09+G8ZE9lQQG5+8GKVXpmJjlNzkarr4dqNsWeKx07kynq0Bl89LJorEX5Qh4p3XW8TGicH483ZDRpo8uxBKuuoMs5PSgErxH7fkQAIwfwfzPMOA4TUmSOXFSU6GMDeIiFShwOL+DCFT8JQyRYG1DxQsADRHNOAPfBqMQNr4K3FFznRzuFf6/rrZXsoqu8EzeY0WnG/AkQDrQbskUFiXyr7h/R3LJHZPyYn5Bv1r2xW2tFeoLx7MGYQqCIM6Klpj4BAoIBAQDc5VuoUeJyCiDzLcVZqi3nsX0wR7a0bAOMrrvFHy+2v2SV4A9SSgF3x/u2n18UNbGqj036+n0YcZiWcrsOEdYaOUwSbZt/Ggax/VeRMLJTcPxXU8GbPQM74euxAKWMtaAXb4GrkgmzfTM4H/f5puAgzXplBOf4Cmg4vEqbFDlp+0CaM1f0QjivwV6s0AWwZHolOAOpbEhWN6zuAJq/k1+/yaX2FU2V4ZN4XUXd/LtfOWDV2d9riGv6X34BbA/ozAACmLiD4ioqLZUw4Y2KyRh3MiYWt9yd6kvHzpDYOWxtJP6R56KKGJkXIZEujp/FJ+diQTygoYBiUHYhP6Agv+X7AoIBAQCY09dwTYAAPkx0mzcBupXPrsaf2oEHgMTQJ+fhcFkOf0Qj6QAy9W3ifetyyl7+ZH4KG2EB3xgKZO4+OtHPshaTHpR+iZxHoou3vCMHFfpjKXIR3JzYlCbPwrt9ZgGUdn644yKHHU0kVrrljXiPl3ZhfIohZffu3J3DhoYRiZtWvcao3XE3/DYQgH4hvn7e0qpj+/m8pLd077lu3JxyDioTNkEDn+09rAM65nYoibMf1DSgjrJot0F4U+YukuXEgYcFuYMJY3repMXqF5jI2+QmBDmNZRsF3biEc/tmN4EBk15eeOfL0dnTmWKkWNkPhLyI0iXOTiPa7c3gbStMfjgpAoIBAQCwEUyefC4fMb2Ttz7fOkr+qzUan36cjp/BqaDW/QWeBy3YXFA6CezjmY4jwSIazLWBOlowmynMqvaxrF82VrcuDc95vM29YHRHuKPsgUESSzOHM6ZU4yvJtkiTu+04m6gwJdvYcj2zSC7kJG8WGZH5824ESQXQahMqbcKVm2gr9m1Gu3f2pDXkuR+KZQCz2VKWifA5QaritdvrlKXD32bpSoQzAkXmGnfd75J53zFGNEjgC5oObx2J4kVgQs7ngnrblvkzfbiYB98rqAyWu7T5hQpg/0okwJSIfThscFDhKiSTDWs74WFc04+R6XKlximi4la9vhzDwFh4JeuvTuWzAoIBAHzA4SrBeO257Tt6wMpef5CLQYSYH5/6U68ABF+ZEHmvFfaiaxEuG8c5twt7e1P6wJCLjQK0FLHXdkvCRCdhXuA1pE4vEy2+2ILhx04veLedy4odJsP9HDmY4ugYbs28dPGEKFlXZ/N0ARkGBRZiM7acrT1aOav416gWljaWuAF05UhPi+6BqbiKy2vMT1Chc4ou1MxTQZbpYdvHpsXMHjiUnznQQR+I3RIlsuqgjWI8HcG6+pRUk9Mf2dTHAV0Jxyi7KOZvGIvKwey32naMWY9W/4wWrpp5RfiJnPzx7hNVM0G3PLC62y/KFCXIiVgLqMOfmFDypH5AhUDgnOnSVxECggEAEpu0uwoDeIWk6L/bG5ciUCiTJhcfKp5EX7h1XYN6DieVxDD1BKvAvcazT4V/7YYJ9DqqheZpuwzhEkAudUun7cy2OeRhwmeUfVI8559bNF9vB7s1G8T71rjD+QCmq6dN9bIif/KuyT7b7EeFV2EBBmwUQN4lz8bznajNVhSWkerDGes+x0lZasHvc9r0qLjXAuNwqktLfJQf9QSIwep99rEMm83wtZD26az6Vi8AezLkP059CVUyT+OOlx7eFbZ/pkI++tFpDflxbT1n85QbTvodwYnK1MmsZiysc65w3mfH/Dd2ivUj0Rc4ShDQVcAgZx33repBDjoW8OXKqQpiMA== + # 鉴权 + 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 + # 针对@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://127.0.0.1:9000 + bucketName: cutejava + accessKey: 8o5oVzqvKby5TzaGcfmx + secretKey: 8BiK5UbmvXGJNVQ98CGiohHzT1N1FMRZqHRdJllj + captcha: + expireTime: 300 + # 序列化白名单 + serial-white-list: + - "org.springframework" + - "cn.odboy.system.dal.dataobject" + - "cn.odboy.system.dal.model" +# debug +#debug: true +# 日志配置 +logging: + logback: + rollingpolicy: + max-file-size: 100MB + max-history: 7 + total-size-cap: 1GB + level: + # root: debug + cn.odboy: info +knife4j: + enable: true + setting: + language: zh_cn diff --git a/cutejava-springboot4/cutejava-starter/src/main/resources/application-prod.yml b/cutejava-springboot4/cutejava-starter/src/main/resources/application-prod.yml new file mode 100644 index 00000000..9521256d --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/main/resources/application-prod.yml @@ -0,0 +1,167 @@ +server: + port: 8000 + http2: + # 启用 HTTP/2 支持,提升传输效率 + enabled: true + 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: + 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: + database: ${REDIS_DB:1} + host: ${REDIS_HOST:127.0.0.1} + port: ${REDIS_PORT:26379} + password: ${REDIS_PWD:kd123456} + # 连接超时时间 + timeout: 5000 + # 连接池配置 + lettuce: + pool: + # 连接池最大连接数 + max-active: 30 + # 连接池最大阻塞等待时间(毫秒),负值表示没有限制 + max-wait: -1 + # 连接池中的最大空闲连接数 + max-idle: 20 + # 连接池中的最小空闲连接数 + min-idle: 1 + repositories: + # 是否启用Redis存储(关闭防止出现 Multiple Spring Data modules found, entering strict repository configuration mode) + enabled: false + datasource: + driverClassName: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:23306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true + username: root + password: kd123456 + hikari: + connection-timeout: 60000 + maximum-pool-size: 10 + minimum-idle: 2 + idle-timeout: 30000 +# 集成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: MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCD3vlcA4vXODkQ5xJIY8hzHDHkPFy88PasFFBrT+jUdMXblyexT25Id/M/6qiGQMbvqQOT7UUD9y5B1rgIMCYrZ9ZarsqCCUp3TyFGQHKMu5Gxe7lZUCkAAbXki00zQKtcjs/jto5qf7pg9TQ2LX7hZhCfDF9Fh1RoqBjkbmBvdL6HHpU7WI3Uq5EwEnvLkUyHVY0k4z1tBvdfWm9onvd2tOnLXAkcC98/zSIbb6xKHQV9w7EKhE0R98JSxTZtg0fp0bqZcgO+jpdlK2ynBR9KK1+A4RHbTu68EIJXIWVqz7ZezT3LICgk2NpaZ4I98Du4m8mtdR+39IzLwk0hcFjjS+XCTh3gUTo4o4b1afShGWh9Epns0a+lTEQDnM5bqDYuwcpRRemA5+7n0zpkfG+IYy3bpbEmKr+J9UokTfJWDHQxUgYQVOsb6yk4JWwNgxIedWw8RR0vqDkVXZgvKo0eWMGpSrDI4R9SKenuE9HGsskMzJfoZbLvLjwOODTDKHaFeuj/KBWtnqNxJ3sxQB3aVi55QorwF7Ls7Mhw4noa94d0vMauyltuScO5GxtwidHlsPqXtgDi+mcuxuI4fbBsolZc2fFHA3kq5ZKnAcMeEBEHx/jBat7BQog7FwUj94nqR7oPnYkw4qGIZ5kqf7b+CIfCs8aRNER7js6x7pS9MwIDAQABAoICAGwcAJJO1fY7QY154qM2Kr8z/jL+yYicg53PiZ5whJCoqwy7fu7+D4uZyvI7jq2BrZYNGcHMYyg9HM8dGQKddq1fE3yEj8DLnSDy3nSZoU9+9pJqvClcbkBzJouXKFM8UUAwVH56y4bD8ChM3eEMq1heNIIvwmtd5vVl2gJr+uNk8k4T5IQitSQFA6JFhPf+xp6spGn5YgJgE604lt1OddH7vy/Y5B76HKxRoASeKMEZycaxCBN3Izf3xnfU9ioR1oL6TeA+eJNgkv19/DVxHZXQuRsPaXRB4baMUk7cMii+4OQXnvFIC6Epptk2wq/EozFVhqGNw9MrycHCKTmgwT1kOGOvl2PZwqC6+aOVLJxyIgvPUhRDIt297A0f86EwMXzQUzd0m/iICmVg0o2bdiyd5SxXv2JG+eB2e+orLeF09+G8ZE9lQQG5+8GKVXpmJjlNzkarr4dqNsWeKx07kynq0Bl89LJorEX5Qh4p3XW8TGicH483ZDRpo8uxBKuuoMs5PSgErxH7fkQAIwfwfzPMOA4TUmSOXFSU6GMDeIiFShwOL+DCFT8JQyRYG1DxQsADRHNOAPfBqMQNr4K3FFznRzuFf6/rrZXsoqu8EzeY0WnG/AkQDrQbskUFiXyr7h/R3LJHZPyYn5Bv1r2xW2tFeoLx7MGYQqCIM6Klpj4BAoIBAQDc5VuoUeJyCiDzLcVZqi3nsX0wR7a0bAOMrrvFHy+2v2SV4A9SSgF3x/u2n18UNbGqj036+n0YcZiWcrsOEdYaOUwSbZt/Ggax/VeRMLJTcPxXU8GbPQM74euxAKWMtaAXb4GrkgmzfTM4H/f5puAgzXplBOf4Cmg4vEqbFDlp+0CaM1f0QjivwV6s0AWwZHolOAOpbEhWN6zuAJq/k1+/yaX2FU2V4ZN4XUXd/LtfOWDV2d9riGv6X34BbA/ozAACmLiD4ioqLZUw4Y2KyRh3MiYWt9yd6kvHzpDYOWxtJP6R56KKGJkXIZEujp/FJ+diQTygoYBiUHYhP6Agv+X7AoIBAQCY09dwTYAAPkx0mzcBupXPrsaf2oEHgMTQJ+fhcFkOf0Qj6QAy9W3ifetyyl7+ZH4KG2EB3xgKZO4+OtHPshaTHpR+iZxHoou3vCMHFfpjKXIR3JzYlCbPwrt9ZgGUdn644yKHHU0kVrrljXiPl3ZhfIohZffu3J3DhoYRiZtWvcao3XE3/DYQgH4hvn7e0qpj+/m8pLd077lu3JxyDioTNkEDn+09rAM65nYoibMf1DSgjrJot0F4U+YukuXEgYcFuYMJY3repMXqF5jI2+QmBDmNZRsF3biEc/tmN4EBk15eeOfL0dnTmWKkWNkPhLyI0iXOTiPa7c3gbStMfjgpAoIBAQCwEUyefC4fMb2Ttz7fOkr+qzUan36cjp/BqaDW/QWeBy3YXFA6CezjmY4jwSIazLWBOlowmynMqvaxrF82VrcuDc95vM29YHRHuKPsgUESSzOHM6ZU4yvJtkiTu+04m6gwJdvYcj2zSC7kJG8WGZH5824ESQXQahMqbcKVm2gr9m1Gu3f2pDXkuR+KZQCz2VKWifA5QaritdvrlKXD32bpSoQzAkXmGnfd75J53zFGNEjgC5oObx2J4kVgQs7ngnrblvkzfbiYB98rqAyWu7T5hQpg/0okwJSIfThscFDhKiSTDWs74WFc04+R6XKlximi4la9vhzDwFh4JeuvTuWzAoIBAHzA4SrBeO257Tt6wMpef5CLQYSYH5/6U68ABF+ZEHmvFfaiaxEuG8c5twt7e1P6wJCLjQK0FLHXdkvCRCdhXuA1pE4vEy2+2ILhx04veLedy4odJsP9HDmY4ugYbs28dPGEKFlXZ/N0ARkGBRZiM7acrT1aOav416gWljaWuAF05UhPi+6BqbiKy2vMT1Chc4ou1MxTQZbpYdvHpsXMHjiUnznQQR+I3RIlsuqgjWI8HcG6+pRUk9Mf2dTHAV0Jxyi7KOZvGIvKwey32naMWY9W/4wWrpp5RfiJnPzx7hNVM0G3PLC62y/KFCXIiVgLqMOfmFDypH5AhUDgnOnSVxECggEAEpu0uwoDeIWk6L/bG5ciUCiTJhcfKp5EX7h1XYN6DieVxDD1BKvAvcazT4V/7YYJ9DqqheZpuwzhEkAudUun7cy2OeRhwmeUfVI8559bNF9vB7s1G8T71rjD+QCmq6dN9bIif/KuyT7b7EeFV2EBBmwUQN4lz8bznajNVhSWkerDGes+x0lZasHvc9r0qLjXAuNwqktLfJQf9QSIwep99rEMm83wtZD26az6Vi8AezLkP059CVUyT+OOlx7eFbZ/pkI++tFpDflxbT1n85QbTvodwYnK1MmsZiysc65w3mfH/Dd2ivUj0Rc4ShDQVcAgZx33repBDjoW8OXKqQpiMA== + # 鉴权 + 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: true + # 验证码 + captchaSetting: + # 验证码类型配置 查看 LoginProperties 类 + code-type: arithmetic + # 登录图形验证码有效时间/分钟 + expiration: 2 + # 验证码高度 + width: 111 + # 验证码宽度 + height: 36 + # 内容长度 + length: 2 + # 字体名称,为空则使用默认字体 + font-name: + # 字体大小 + font-size: 25 + # 针对@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://127.0.0.1:9000 + bucketName: cutejava + accessKey: 8o5oVzqvKby5TzaGcfmx + secretKey: 8BiK5UbmvXGJNVQ98CGiohHzT1N1FMRZqHRdJllj + captcha: + expireTime: 300 + # 序列化白名单 + serial-white-list: + - "org.springframework" + - "cn.odboy.system.dal.dataobject" + - "cn.odboy.system.dal.model" +# debug +#debug: true +# 日志配置 +logging: + logback: + rollingpolicy: + max-file-size: 100MB + max-history: 7 + total-size-cap: 1GB + level: + # root: debug + cn.odboy: info +knife4j: + enable: false + setting: + language: zh_cn diff --git a/cutejava-springboot4/cutejava-starter/src/main/resources/application-quartz.yml b/cutejava-springboot4/cutejava-starter/src/main/resources/application-quartz.yml new file mode 100644 index 00000000..ba858e94 --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/main/resources/application-quartz.yml @@ -0,0 +1,50 @@ +# 分布式定时任务配置 +spring: + # 数据库一定不能区分大小写 + quartz: + # 必需,指定使用 JDBC 存储 + job-store-type: jdbc + properties: + org: + quartz: + scheduler: + # 必需,指定调度器实例的名称 + instanceName: cutejava + # 必需,指定调度器实例的 ID + instanceId: AUTO + threadPool: + # Scheduler使用的线程池名称,实现了ThreadPool接口,参考org.quartz.simpl.SimpleThreadPool + class: org.quartz.simpl.SimpleThreadPool + # 可选,并发个数(1~100) + threadCount: 10 + # 可选,线程的优先级(1~10) + threadPriority: 5 + # 可选,指定在线程池里面创建的线程是否是守护线程 + makeThreadsDaemons: false + # 可选,线程上下文类加载器是否继承自初始线程的加载器 + threadsInheritContextClassLoaderOfInitializingThread: true + dataSource: + jobStoreDataSource: + provider: hikaricp + driver: com.mysql.cj.jdbc.Driver + URL: jdbc:mysql://127.0.0.1:3306/cutejava?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true + user: root + password: kd123456 + maxConnections: 10 + validationQuery: select 1 + jobStore: + # 可选,如果你不需要集群,可以去掉 + isClustered: true + # 可选,集群检查间隔时间,可以根据需要调整 + clusterCheckinInterval: 10000 + # 可选,分布式事务类 + # class: org.quartz.impl.jdbcjobstore.JobStoreTX + # 必需,指定 JDBC 驱动代理类 + driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate + # 可选,是否使用属性存储,可以根据需要调整 + useProperties: false + # 必需,指定表的前缀 + tablePrefix: qrtz_ + # 可选,指定误触发阈值,可以根据需要调整 + misfireThreshold: 60000 + dataSource: jobStoreDataSource diff --git a/cutejava-springboot4/cutejava-starter/src/main/resources/application.yml b/cutejava-springboot4/cutejava-starter/src/main/resources/application.yml new file mode 100644 index 00000000..c67c67b4 --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/main/resources/application.yml @@ -0,0 +1,6 @@ +spring: + profiles: + # 激活的环境 + active: dev +# active: dev,quartz + diff --git a/cutejava-springboot4/cutejava-starter/src/main/resources/banner.txt b/cutejava-springboot4/cutejava-starter/src/main/resources/banner.txt new file mode 100644 index 00000000..4eeb300a --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/main/resources/banner.txt @@ -0,0 +1,8 @@ + _____ _ _ _____ _____ _ ___ _ _ ___ +/ ___| | | | | |_ _| | ____| | | / | | | / / / | +| | | | | | | | | |__ | | / /| | | | / / / /| | +| | | | | | | | | __| _ | | / / | | | | / / / / | | +| |___ | |_| | | | | |___ | |_| | / / | | | |/ / / / | | +\_____| \_____/ |_| |_____| \_____/ /_/ |_| |___/ /_/ |_| + +Spring Boot Version: (${spring-boot.version}),CuteJava Version: (1.5.2) \ No newline at end of file diff --git a/cutejava-springboot4/cutejava-starter/src/main/resources/logback.xml b/cutejava-springboot4/cutejava-starter/src/main/resources/logback.xml new file mode 100644 index 00000000..b43a331e --- /dev/null +++ b/cutejava-springboot4/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} + + + + + + + + + + diff --git a/cutejava-springboot4/cutejava-starter/src/main/resources/spy.properties b/cutejava-springboot4/cutejava-starter/src/main/resources/spy.properties new file mode 100644 index 00000000..784f0677 --- /dev/null +++ b/cutejava-springboot4/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-springboot4/cutejava-starter/src/test/java/cn/odboy/MyBatisMapperTests.java b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/MyBatisMapperTests.java new file mode 100644 index 00000000..0fd9393b --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/MyBatisMapperTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021-2026 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 java.util.Date; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@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-springboot4/cutejava-starter/src/test/java/cn/odboy/SystemApplicationTests.java b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/SystemApplicationTests.java new file mode 100644 index 00000000..a47057b1 --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/SystemApplicationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2026 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.SystemJobTb; +import cn.odboy.system.dal.dataobject.SystemRoleTb; +import cn.odboy.system.dal.model.response.SystemDeptVo; +import cn.odboy.system.dal.model.response.SystemUserVo; +import cn.odboy.system.service.SystemUserService; +import java.util.Set; +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; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SystemApplicationTests { + + @Autowired + private PasswordEncoder passwordEncoder; + @Autowired + private SystemUserService systemUserService; + + public static void main(String[] args) { + } + + @Test + public void contextLoads() { + SystemDeptVo systemDept = new SystemDeptVo(); + 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++) { + SystemUserVo systemUser = new SystemUserVo(); + 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); + } + } +} diff --git a/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitDateUtilTest.java b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitDateUtilTest.java new file mode 100644 index 00000000..83defe42 --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitDateUtilTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2026 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 static org.junit.jupiter.api.Assertions.assertEquals; + +import java.text.SimpleDateFormat; +import java.util.Date; +import org.junit.jupiter.api.Test; + +public class KitDateUtilTest { + + @Test + public void testGetWeekDay() { + SimpleDateFormat simpleDateformat = new SimpleDateFormat("E"); + assertEquals(simpleDateformat.format(new Date()), KitDateUtil.getWeekDay()); + } +} diff --git a/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitDesEncryptUtilTest.java b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitDesEncryptUtilTest.java new file mode 100644 index 00000000..8dafe8c6 --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitDesEncryptUtilTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2026 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 static cn.odboy.util.KitDesEncryptUtil.desDecrypt; +import static cn.odboy.util.KitDesEncryptUtil.desEncrypt; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +public class KitDesEncryptUtilTest { + + @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-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitFileUtilTest.java b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitFileUtilTest.java new file mode 100644 index 00000000..dfefcc96 --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitFileUtilTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021-2026 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 static cn.odboy.util.KitFileUtil.getPrefix; +import static cn.odboy.util.KitFileUtil.getSize; +import static cn.odboy.util.KitFileUtil.getSuffix; +import static cn.odboy.util.KitFileUtil.toFile; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockMultipartFile; + +public class KitFileUtilTest { + + @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-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitStringUtilTest.java b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitStringUtilTest.java new file mode 100644 index 00000000..f2ccea16 --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/KitStringUtilTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2026 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 cn.odboy.util.KitStringUtil.toCamelCase; +import static cn.odboy.util.KitStringUtil.toCapitalizeCamelCase; +import static cn.odboy.util.KitStringUtil.toUnderScoreCase; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class KitStringUtilTest { + + @Test + public void testToCamelCase() { + assertNull(toCamelCase(null)); + } + + @Test + public void testToCapitalizeCamelCase() { + assertNull(KitStringUtil.toCapitalizeCamelCase(null)); + assertEquals("HelloWorld", toCapitalizeCamelCase("hello_world")); + } + + @Test + public void testToUnderScoreCase() { + assertNull(KitStringUtil.toUnderScoreCase(null)); + assertEquals("hello_world", toUnderScoreCase("helloWorld")); + assertEquals("\u0000\u0000", toUnderScoreCase("\u0000\u0000")); + assertEquals("\u0000_a", toUnderScoreCase("\u0000A")); + } + + @Test + public void testGetWeekDay() { + SimpleDateFormat simpleDateformat = new SimpleDateFormat("E"); + assertEquals(simpleDateformat.format(new Date()), KitDateUtil.getWeekDay()); + } + + @Test + public void testGetIP() { + Assertions.assertEquals("127.0.0.1", KitBrowserUtil.getIp(new MockHttpServletRequest())); + } +} diff --git a/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/WorkTimeTests.java b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/WorkTimeTests.java new file mode 100644 index 00000000..2dedc135 --- /dev/null +++ b/cutejava-springboot4/cutejava-starter/src/test/java/cn/odboy/util/WorkTimeTests.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2026 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-springboot4/pom.xml b/cutejava-springboot4/pom.xml new file mode 100644 index 00000000..fe409d69 --- /dev/null +++ b/cutejava-springboot4/pom.xml @@ -0,0 +1,405 @@ + + + 4.0.0 + cn.odboy + cutejava-springboot4 + pom + 1.5.2 + + + + cutejava-framework + cutejava-module-system + cutejava-starter + + + CuteJava 后台 + + + org.springframework.boot + spring-boot-starter-parent + 4.1.0-M2 + + + + UTF-8 + UTF-8 + UTF-8 + 21 + 2.0.59 + 2.11.1 + 3.5.16 + 5.8.43 + 0.13.0 + + 5.8.0 + 1.4.7 + 2.3.34 + 3.9.1 + 9.5.0 + 3.5.7 + 1.9.25.1 + 1.6.2 + 2.14.5 + build210 + 6.9.2 + 2.5.2 + 0.11.2 + 3.12.0 + 2.0.3.RELEASE + 1.3.0 + 1.8.0 + 33.5.0-jre + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-json + + + + + + + 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-spring-boot4-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 + ${freemarker.version} + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus.version} + + + + + org.apache.commons + commons-pool2 + ${commons-pool2.version} + + + org.apache.commons + commons-lang3 + 3.20.0 + + + + + p6spy + p6spy + ${p6spy.version} + + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + 4.5.0 + + + + + com.mysql + mysql-connector-j + ${mysql.version} + runtime + + + + + net.dreamlu + mica-ip2region + ${ip2region.version} + + + + + org.aspectj + aspectjrt + ${springaop.version} + + + + org.aspectj + aspectjweaver + ${springaop.version} + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson2.version} + + + com.alibaba.fastjson2 + fastjson2-extension-spring6 + ${fastjson2.version} + + + + + com.github.whvcse + easy-captcha + ${captcha.version} + + + + + com.alibaba + transmittable-thread-local + ${transmittable.version} + + + + org.springframework.boot + spring-boot-starter-aop + 4.0.0-M2 + + + + + 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 + ${ganymed-ssh2.version} + + + + + + + + com.github.mwiede + jsch + 2.27.7 + + + + + com.github.oshi + oshi-core + ${oshi.version} + + + + + javax.mail + mail + ${mail.version} + + + + + org.springframework.boot + spring-boot-starter-quartz + + + org.quartz-scheduler + quartz + + + + + org.quartz-scheduler + quartz + ${quartz.version} + + + + + + + + com.zaxxer + HikariCP + 7.0.2 + compile + + + + + commons-net + commons-net + ${commons-net.version} + + + + + io.github.biezhi + TinyPinyin + ${pinyin.version} + + + + + + cn.idev.excel + fastexcel + ${fastexcel.version} + compile + + + + + + + + com.ezylang + EvalEx + 3.3.0 + + + + com.google.guava + guava + ${guava.version} + + + + io.swagger + swagger-annotations + 1.5.22 + + + + + + + + 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 + + + +