From 1bfabf1ffafa5850a2a7cecca2ce545dbb4bc2fa Mon Sep 17 00:00:00 2001 From: fengshuonan Date: Fri, 11 Dec 2020 18:18:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 40 ++ README.md | 131 +++++ kernel-a-rule/README.md | 18 + kernel-a-rule/pom.xml | 83 +++ .../rule/abstracts/AbstractExceptionEnum.java | 33 ++ .../rule/abstracts/AbstractTreeNode.java | 40 ++ .../kernel/rule/constants/RuleConstants.java | 52 ++ .../kernel/rule/constants/TreeConstants.java | 21 + .../roses/kernel/rule/enums/SexEnum.java | 49 ++ .../roses/kernel/rule/enums/StatusEnum.java | 74 +++ .../roses/kernel/rule/enums/YesOrNotEnum.java | 33 ++ .../rule/exception/base/ServiceException.java | 100 ++++ .../DefaultBusinessExceptionEnum.java | 38 ++ .../defaults/DefaultThirdExceptionEnum.java | 38 ++ .../defaults/DefaultUserExceptionEnum.java | 38 ++ .../enums/http/ServletExceptionEnum.java | 38 ++ .../rule/factory/DefaultTreeBuildFactory.java | 94 ++++ .../kernel/rule/factory/TreeNodeFactory.java | 32 ++ .../base/AbstractTreeBuildFactory.java | 23 + .../kernel/rule/pojo/dict/SimpleDict.java | 35 ++ .../kernel/rule/pojo/request/BaseRequest.java | 132 +++++ .../rule/pojo/response/ErrorResponseData.java | 28 ++ .../rule/pojo/response/ResponseData.java | 44 ++ .../pojo/response/SuccessResponseData.java | 24 + .../rule/pojo/tree/DefaultTreeNode.java | 70 +++ .../kernel/rule/util/AopTargetUtils.java | 75 +++ .../kernel/rule/util/HttpServletUtil.java | 162 ++++++ kernel-d-auth/README.md | 11 + kernel-d-auth/auth-api/README.md | 1 + kernel-d-auth/auth-api/pom.xml | 52 ++ .../roses/kernel/auth/api/AuthServiceApi.java | 81 +++ .../roses/kernel/auth/api/LoginUserApi.java | 86 ++++ .../kernel/auth/api/PermissionServiceApi.java | 26 + .../kernel/auth/api/SessionManagerApi.java | 76 +++ .../auth/api/constants/AuthConstants.java | 46 ++ .../kernel/auth/api/context/LoginContext.java | 18 + .../auth/api/enums/DataScopeTypeEnum.java | 70 +++ .../auth/api/exception/AuthException.java | 27 + .../exception/enums/AuthExceptionEnum.java | 87 ++++ .../auth/api/expander/AuthConfigExpander.java | 91 ++++ .../auth/api/pojo/auth/LoginRequest.java | 32 ++ .../auth/api/pojo/auth/LoginResponse.java | 30 ++ .../kernel/auth/api/pojo/login/LoginUser.java | 117 +++++ kernel-d-auth/auth-sdk/README.md | 1 + kernel-d-auth/auth-sdk/pom.xml | 72 +++ .../kernel/auth/auth/AuthServiceImpl.java | 186 +++++++ .../roses/kernel/auth/auth/LoginUserImpl.java | 142 ++++++ .../permission/PermissionServiceImpl.java | 63 +++ .../session/MemoryCacheSessionManager.java | 146 ++++++ .../auth/session/RedisSessionManager.java | 159 ++++++ .../auth-spring-boot-starter/README.md | 1 + .../auth-spring-boot-starter/pom.xml | 51 ++ .../starter/GunsAuthAutoConfiguration.java | 35 ++ .../main/resources/META-INF/spring.factories | 2 + kernel-d-auth/pom.xml | 35 ++ kernel-d-cache/README.md | 7 + kernel-d-cache/cache-api/README.md | 3 + kernel-d-cache/cache-api/pom.xml | 42 ++ .../kernel/cache/api/CacheOperatorApi.java | 104 ++++ .../cache/api/constants/CacheConstants.java | 26 + kernel-d-cache/cache-sdk-memory/README.md | 1 + kernel-d-cache/cache-sdk-memory/pom.xml | 29 ++ .../cache/AbstractMemoryCacheOperator.java | 86 ++++ kernel-d-cache/cache-sdk-redis/README.md | 1 + kernel-d-cache/cache-sdk-redis/pom.xml | 45 ++ .../cache/AbstractRedisCacheOperator.java | 85 ++++ kernel-d-cache/pom.xml | 35 ++ kernel-d-config/README.md | 7 + kernel-d-config/config-api/README.md | 1 + kernel-d-config/config-api/pom.xml | 42 ++ .../roses/kernel/config/api/ConfigApi.java | 101 ++++ .../config/api/constants/ConfigConstants.java | 21 + .../config/api/context/ConfigContext.java | 45 ++ .../config/api/exception/ConfigException.java | 27 + .../exception/enums/ConfigExceptionEnum.java | 81 +++ kernel-d-config/config-business/README.md | 1 + kernel-d-config/config-business/pom.xml | 59 +++ .../controller/SysConfigController.java | 101 ++++ .../config/modular/entity/SysConfig.java | 102 ++++ .../modular/listener/ConfigInitListener.java | 95 ++++ .../modular/mapper/SysConfigMapper.java | 15 + .../mapper/mapping/SysConfigMapper.xml | 6 + .../config/modular/param/SysConfigParam.java | 86 ++++ .../modular/service/SysConfigService.java | 99 ++++ .../service/impl/SysConfigServiceImpl.java | 157 ++++++ kernel-d-config/config-sdk-db/README.md | 1 + kernel-d-config/config-sdk-db/pom.xml | 29 ++ .../roses/kernel/config/ConfigContainer.java | 105 ++++ .../config-spring-boot-starter/README.md | 1 + .../config-spring-boot-starter/pom.xml | 51 ++ .../GunsSysConfigAutoConfiguration.java | 15 + .../main/resources/META-INF/spring.factories | 4 + kernel-d-config/pom.xml | 36 ++ kernel-d-db/README.md | 3 + kernel-d-db/db-api/README.md | 1 + kernel-d-db/db-api/pom.xml | 63 +++ .../roses/kernel/db/api/DbOperatorApi.java | 43 ++ .../kernel/db/api/constants/DbConstants.java | 21 + .../db/api/constants/DbFieldConstants.java | 41 ++ .../db/api/context/DbOperatorContext.java | 18 + .../roses/kernel/db/api/enums/DbTypeEnum.java | 51 ++ .../kernel/db/api/exception/DaoException.java | 19 + .../api/exception/enums/DaoExceptionEnum.java | 37 ++ .../kernel/db/api/factory/DruidFactory.java | 109 ++++ .../kernel/db/api/factory/PageFactory.java | 55 ++ .../db/api/factory/PageResultFactory.java | 35 ++ .../db/api/pojo/druid/DruidProperties.java | 130 +++++ .../kernel/db/api/pojo/entity/BaseEntity.java | 49 ++ .../kernel/db/api/pojo/page/PageResult.java | 44 ++ kernel-d-db/db-sdk-mp/README.md | 1 + kernel-d-db/db-sdk-mp/pom.xml | 37 ++ .../db/mp/dbid/CustomDatabaseIdProvider.java | 37 ++ .../db/mp/dboperator/DbOperatorImpl.java | 38 ++ .../mp/fieldfill/CustomMetaObjectHandler.java | 75 +++ kernel-d-db/db-spring-boot-starter/README.md | 1 + kernel-d-db/db-spring-boot-starter/pom.xml | 51 ++ .../GunsDataSourceAutoConfiguration.java | 38 ++ .../GunsDruidPropertiesAutoConfiguration.java | 34 ++ .../GunsMyBatisPlusAutoConfiguration.java | 55 ++ .../main/resources/META-INF/spring.factories | 4 + kernel-d-db/pom.xml | 35 ++ kernel-d-ds-container/README.md | 1 + .../ds-container-api/README.md | 1 + .../ds-container-api/pom.xml | 43 ++ .../dsctn/api/annotation/DataSource.java | 21 + .../DatasourceContainerConstants.java | 36 ++ .../api/context/CurrentDataSourceContext.java | 43 ++ .../DatasourceContainerException.java | 23 + .../DatasourceContainerExceptionEnum.java | 102 ++++ .../ds-container-business/README.md | 1 + .../ds-container-business/pom.xml | 59 +++ .../controller/DatabaseInfoController.java | 83 +++ .../dsctn/modular/entity/DatabaseInfo.java | 66 +++ .../dsctn/modular/factory/DruidFactory.java | 29 ++ .../modular/mapper/DatabaseInfoMapper.java | 14 + .../mapper/mapping/DatabaseInfoMapper.xml | 5 + .../dsctn/modular/pojo/DatabaseInfoParam.java | 75 +++ .../modular/service/DatabaseInfoService.java | 52 ++ .../service/impl/DatabaseInfoServiceImpl.java | 203 ++++++++ .../ds-container-sdk/README.md | 1 + .../ds-container-sdk/pom.xml | 43 ++ .../dsctn/AbstractRoutingDataSource.java | 47 ++ .../roses/kernel/dsctn/DynamicDataSource.java | 42 ++ .../dsctn/aop/MultiSourceExchangeAop.java | 79 +++ .../dsctn/context/DataSourceContext.java | 142 ++++++ .../listener/DataSourceInitListener.java | 79 +++ .../persist/DataBaseInfoPersistence.java | 150 ++++++ .../dsctn/persist/sqls/AbstractSql.java | 70 +++ .../persist/sqls/AddDatabaseInfoSql.java | 33 ++ .../dsctn/persist/sqls/CreateDatabaseSql.java | 33 ++ .../dsctn/persist/sqls/DatabaseListSql.java | 33 ++ .../persist/sqls/DeleteDatabaseInfoSql.java | 33 ++ .../dsctn/persist/sqls/DropDatabaseSql.java | 33 ++ .../dsctn/persist/sqls/TableFieldListSql.java | 49 ++ .../dsctn/persist/sqls/TableListSql.java | 52 ++ .../README.md | 1 + .../ds-container-spring-boot-starter/pom.xml | 51 ++ ...sDataSourceContainerAutoConfiguration.java | 41 ++ .../main/resources/META-INF/spring.factories | 2 + kernel-d-ds-container/pom.xml | 36 ++ kernel-d-email/README.md | 1 + kernel-d-email/email-api/README.md | 1 + kernel-d-email/email-api/pom.xml | 50 ++ .../roses/kernel/email/api/MailSenderApi.java | 31 ++ .../email/api/constants/MailConstants.java | 61 +++ .../email/api/context/MailSenderContext.java | 24 + .../email/api/exception/MailException.java | 29 ++ .../exception/enums/EmailExceptionEnum.java | 42 ++ .../api/expander/EmailConfigExpander.java | 124 +++++ .../kernel/email/api/pojo/SendMailParam.java | 29 ++ .../aliyun/AliyunMailSenderProperties.java | 29 ++ .../api/pojo/aliyun/AliyunSendMailParam.java | 51 ++ kernel-d-email/email-sdk-aliyun/README.md | 1 + kernel-d-email/email-sdk-aliyun/pom.xml | 41 ++ .../kernel/email/aliyun/AliyunMailSender.java | 224 +++++++++ kernel-d-email/email-sdk-java/README.md | 1 + kernel-d-email/email-sdk-java/pom.xml | 36 ++ .../roses/kernel/email/JavaMailSender.java | 78 +++ .../email-spring-boot-starter/README.md | 1 + .../email-spring-boot-starter/pom.xml | 51 ++ .../starter/GunsEmailAutoConfiguration.java | 47 ++ .../main/resources/META-INF/spring.factories | 2 + kernel-d-email/pom.xml | 36 ++ kernel-d-file/README.md | 3 + kernel-d-file/file-api/README.md | 1 + kernel-d-file/file-api/pom.xml | 64 +++ .../roses/kernel/file/FileInfoApi.java | 23 + .../roses/kernel/file/FileOperatorApi.java | 156 ++++++ .../kernel/file/constants/FileConstants.java | 46 ++ .../kernel/file/context/FileContext.java | 24 + .../kernel/file/enums/BucketAuthEnum.java | 33 ++ .../kernel/file/enums/FileLocationEnum.java | 40 ++ .../kernel/file/exception/FileException.java | 27 + .../exception/enums/FileExceptionEnum.java | 87 ++++ .../file/expander/FileConfigExpander.java | 75 +++ .../file/pojo/props/AliyunOssProperties.java | 31 ++ .../file/pojo/props/LocalFileProperties.java | 24 + .../file/pojo/props/MinIoProperties.java | 29 ++ .../file/pojo/props/TenCosProperties.java | 29 ++ .../file/pojo/request/SysFileInfoRequest.java | 68 +++ .../pojo/response/SysFileInfoResponse.java | 59 +++ .../roses/kernel/file/util/DownloadUtil.java | 76 +++ .../kernel/file/util/PicFileTypeUtil.java | 50 ++ kernel-d-file/file-business/README.md | 1 + kernel-d-file/file-business/pom.xml | 59 +++ .../controller/SysFileInfoController.java | 139 +++++ .../file/modular/entity/SysFileInfo.java | 78 +++ .../modular/mapper/SysFileInfoMapper.java | 16 + .../mapper/mapping/SysFileInfoMapper.xml | 5 + .../modular/service/SysFileInfoService.java | 110 ++++ .../service/impl/SysFileInfoServiceImpl.java | 306 +++++++++++ kernel-d-file/file-sdk-aliyun/README.md | 1 + kernel-d-file/file-sdk-aliyun/pom.xml | 35 ++ .../file/aliyun/AliyunFileOperator.java | 200 ++++++++ kernel-d-file/file-sdk-local/README.md | 1 + kernel-d-file/file-sdk-local/pom.xml | 37 ++ .../kernel/file/local/LocalFileOperator.java | 179 +++++++ kernel-d-file/file-sdk-minio/README.md | 1 + kernel-d-file/file-sdk-minio/pom.xml | 47 ++ .../kernel/file/minio/MinIoFileOperator.java | 275 ++++++++++ kernel-d-file/file-sdk-tencent/README.md | 1 + kernel-d-file/file-sdk-tencent/pom.xml | 39 ++ .../kernel/file/tencent/TenFileOperator.java | 247 +++++++++ .../file-spring-boot-starter/README.md | 1 + .../file-spring-boot-starter/pom.xml | 58 +++ .../starter/GunsFileAutoConfiguration.java | 39 ++ .../main/resources/META-INF/spring.factories | 2 + kernel-d-file/pom.xml | 39 ++ kernel-d-jwt/README.md | 3 + kernel-d-jwt/jwt-api/README.md | 1 + kernel-d-jwt/jwt-api/pom.xml | 55 ++ .../roses/kernel/jwt/api/JwtApi.java | 86 ++++ .../jwt/api/constants/JwtConstants.java | 26 + .../kernel/jwt/api/context/JwtContext.java | 24 + .../jwt/api/exception/JwtException.java | 23 + .../api/exception/enums/JwtExceptionEnum.java | 37 ++ .../jwt/api/expander/JwtConfigExpander.java | 43 ++ .../kernel/jwt/api/pojo/config/JwtConfig.java | 24 + .../api/pojo/payload/DefaultJwtPayload.java | 52 ++ kernel-d-jwt/jwt-sdk/README.md | 1 + kernel-d-jwt/jwt-sdk/pom.xml | 29 ++ .../roses/kernel/jwt/JwtTokenOperator.java | 112 +++++ .../jwt-spring-boot-starter/README.md | 1 + kernel-d-jwt/jwt-spring-boot-starter/pom.xml | 51 ++ .../jwt/starter/GunsJwtAutoConfiguration.java | 39 ++ .../main/resources/META-INF/spring.factories | 2 + kernel-d-jwt/pom.xml | 35 ++ kernel-d-log/README.md | 5 + kernel-d-log/log-api/README.md | 1 + kernel-d-log/log-api/pom.xml | 77 +++ .../roses/kernel/log/api/LogManagerApi.java | 50 ++ .../roses/kernel/log/api/LogRecordApi.java | 42 ++ .../log/api/constants/LogConstants.java | 31 ++ .../log/api/constants/LogFileConstants.java | 43 ++ .../log/api/context/LogRecordContext.java | 24 + .../log/api/context/ServerInfoContext.java | 39 ++ .../log/api/exception/LogException.java | 19 + .../api/exception/enums/LogExceptionEnum.java | 52 ++ .../log/api/expander/LogConfigExpander.java | 48 ++ .../log/api/factory/LogRecordFactory.java | 53 ++ .../factory/appender/AuthedLogAppender.java | 39 ++ .../api/factory/appender/HttpLogAppender.java | 62 +++ .../factory/appender/ParamsLogAppender.java | 31 ++ .../log/api/pojo/manage/LogManagerParam.java | 82 +++ .../log/api/pojo/record/LogRecordDTO.java | 100 ++++ .../api/threadpool/LogManagerThreadPool.java | 42 ++ kernel-d-log/log-business-manage/README.md | 1 + kernel-d-log/log-business-manage/pom.xml | 59 +++ .../controller/LogManagerController.java | 56 +++ .../log-business-requestapi/README.md | 3 + kernel-d-log/log-business-requestapi/pom.xml | 43 ++ .../aop/RequestApiLogRecordAop.java | 159 ++++++ kernel-d-log/log-sdk-db/README.md | 1 + kernel-d-log/log-sdk-db/pom.xml | 36 ++ .../log/db/DbLogManagerServiceImpl.java | 200 ++++++++ .../kernel/log/db/DbLogRecordServiceImpl.java | 283 +++++++++++ .../roses/kernel/log/db/entity/SysLog.java | 114 +++++ .../kernel/log/db/mapper/SysLogMapper.java | 14 + .../log/db/mapper/mapping/SysLogMapper.xml | 4 + .../kernel/log/db/service/SysLogService.java | 14 + .../db/service/impl/SysLogServiceImpl.java | 18 + kernel-d-log/log-sdk-file/README.md | 1 + kernel-d-log/log-sdk-file/pom.xml | 45 ++ .../log/file/FileLogManagerServiceImpl.java | 275 ++++++++++ .../log/file/FileLogRecordServiceImpl.java | 329 ++++++++++++ .../log-spring-boot-starter/README.md | 1 + kernel-d-log/log-spring-boot-starter/pom.xml | 65 +++ .../log/starter/GunsLogAutoConfiguration.java | 14 + .../main/resources/META-INF/spring.factories | 2 + kernel-d-log/pom.xml | 38 ++ kernel-d-office/README.md | 1 + kernel-d-office/office-api/README.md | 1 + kernel-d-office/office-api/pom.xml | 52 ++ .../kernel/office/api/OfficeExcelApi.java | 46 ++ .../office/api/constants/OfficeConstants.java | 31 ++ .../office/api/exception/OfficeException.java | 23 + .../exception/enums/OfficeExceptionEnum.java | 47 ++ .../api/pojo/report/ExcelExportParam.java | 53 ++ kernel-d-office/office-sdk-excel/README.md | 1 + kernel-d-office/office-sdk-excel/pom.xml | 39 ++ .../kernel/office/excel/OfficeExcel.java | 138 +++++ .../excel/listener/SimpleDataListener.java | 58 +++ .../office-spring-boot-starter/README.md | 1 + .../office-spring-boot-starter/pom.xml | 51 ++ kernel-d-office/pom.xml | 42 ++ kernel-d-pinyin/README.md | 1 + kernel-d-pinyin/pinyin-api/README.md | 1 + kernel-d-pinyin/pinyin-api/pom.xml | 42 ++ .../roses/kernel/pinyin/api/PinYinApi.java | 72 +++ .../pinyin/api/constants/PinyinConstants.java | 26 + .../pinyin/api/context/PinyinContext.java | 24 + .../pinyin/api/exception/PinyinException.java | 25 + .../exception/enums/PinyinExceptionEnum.java | 37 ++ kernel-d-pinyin/pinyin-sdk-pinyin4j/README.md | 1 + kernel-d-pinyin/pinyin-sdk-pinyin4j/pom.xml | 36 ++ .../kernel/pinyin/PinyinServiceImpl.java | 192 +++++++ .../pinyin-spring-boot-starter/README.md | 1 + .../pinyin-spring-boot-starter/pom.xml | 79 +++ .../starter/GunsPinyinAutoConfiguration.java | 30 ++ .../main/resources/META-INF/spring.factories | 2 + kernel-d-pinyin/pom.xml | 35 ++ kernel-d-scanner/README.md | 5 + kernel-d-scanner/pom.xml | 35 ++ kernel-d-scanner/scanner-api/README.md | 1 + kernel-d-scanner/scanner-api/pom.xml | 35 ++ .../resource/api/ResourceCollectorApi.java | 107 ++++ .../resource/api/ResourceReportApi.java | 30 ++ .../resource/api/annotation/ApiResource.java | 76 +++ .../resource/api/annotation/GetResource.java | 72 +++ .../resource/api/annotation/PostResource.java | 70 +++ .../annotation/field/ChineseDescription.java | 23 + .../api/constants/ScannerConstants.java | 26 + .../api/exception/ScannerException.java | 27 + .../exception/enums/ScannerExceptionEnum.java | 42 ++ .../api/holder/InitScanFlagHolder.java | 21 + .../resource/api/holder/IpAddrHolder.java | 27 + .../api/pojo/resource/FieldDescription.java | 45 ++ .../pojo/resource/ReportResourceParam.java | 36 ++ .../api/pojo/resource/ResourceDefinition.java | 112 +++++ .../api/pojo/resource/ResourceUrlParam.java | 19 + .../api/pojo/resource/UserResourceParam.java | 22 + .../api/pojo/scanner/ScannerProperties.java | 43 ++ .../resource/api/util/ClassReflectUtil.java | 146 ++++++ .../resource/api/util/MethodReflectUtil.java | 141 ++++++ .../scanner-sdk-scanner/README.md | 1 + kernel-d-scanner/scanner-sdk-scanner/pom.xml | 35 ++ .../resource/scanner/ApiResourceScanner.java | 321 ++++++++++++ .../scanner/DefaultResourceCollector.java | 121 +++++ .../scanner/ResourceReportListener.java | 58 +++ .../scanner-spring-boot-starter/README.md | 1 + .../scanner-spring-boot-starter/pom.xml | 51 ++ .../GunsResourceAutoConfiguration.java | 70 +++ .../main/resources/META-INF/spring.factories | 4 + kernel-d-sms/README.md | 1 + kernel-d-sms/pom.xml | 37 ++ kernel-d-sms/sms-api/README.md | 1 + kernel-d-sms/sms-api/pom.xml | 49 ++ .../roses/kernel/sms/api/SmsSenderApi.java | 26 + .../sms/api/constants/SmsConstants.java | 28 ++ .../kernel/sms/api/context/SmsContext.java | 24 + .../sms/api/exception/SmsException.java | 23 + .../api/exception/enums/SmsExceptionEnum.java | 72 +++ .../sms/api/expander/SmsConfigExpander.java | 55 ++ .../sms/api/pojo/AliyunSmsProperties.java | 49 ++ .../sms/api/pojo/TencentSmsProperties.java | 40 ++ .../sms-business-validation/README.md | 1 + kernel-d-sms/sms-business-validation/pom.xml | 59 +++ .../controller/SmsSenderController.java | 77 +++ .../kernel/sms/modular/entity/SysSms.java | 75 +++ .../sms/modular/enums/SmsSendSourceEnum.java | 34 ++ .../sms/modular/enums/SmsSendStatusEnum.java | 66 +++ .../kernel/sms/modular/enums/SmsTypeEnum.java | 56 +++ .../sms/modular/mapper/SysSmsMapper.java | 14 + .../modular/mapper/mapping/SysSmsMapper.xml | 6 + .../sms/modular/param/SysSmsInfoParam.java | 58 +++ .../sms/modular/param/SysSmsSendParam.java | 46 ++ .../sms/modular/param/SysSmsVerifyParam.java | 40 ++ .../modular/service/SysSmsInfoService.java | 71 +++ .../service/impl/SysSmsInfoServiceImpl.java | 195 +++++++ kernel-d-sms/sms-sdk-aliyun/README.md | 1 + kernel-d-sms/sms-sdk-aliyun/pom.xml | 39 ++ .../roses/kernel/aliyun/AliyunSmsSender.java | 183 +++++++ .../aliyun/enums/AliyunSmsResultEnum.java | 124 +++++ .../kernel/aliyun/msign/MultiSignManager.java | 22 + .../msign/impl/MapBasedMultiSignManager.java | 64 +++ kernel-d-sms/sms-sdk-tencent/README.md | 1 + kernel-d-sms/sms-sdk-tencent/pom.xml | 35 ++ .../kernel/sms/tencent/TencentSmsSender.java | 94 ++++ .../sms-spring-boot-starter/README.md | 1 + kernel-d-sms/sms-spring-boot-starter/pom.xml | 58 +++ .../sms/starter/GunsSmsAutoConfiguration.java | 41 ++ .../main/resources/META-INF/spring.factories | 2 + kernel-d-timer/README.md | 1 + kernel-d-timer/pom.xml | 36 ++ kernel-d-timer/timer-api/README.md | 1 + kernel-d-timer/timer-api/pom.xml | 42 ++ .../roses/kernel/timer/api/TimerAction.java | 21 + .../kernel/timer/api/TimerExeService.java | 35 ++ .../timer/api/constants/TimerConstants.java | 21 + .../timer/api/enums/TimerJobStatusEnum.java | 54 ++ .../timer/api/exception/TimerException.java | 23 + .../exception/enums/TimerExceptionEnum.java | 48 ++ kernel-d-timer/timer-business/README.md | 1 + kernel-d-timer/timer-business/pom.xml | 59 +++ .../controller/SysTimersController.java | 136 +++++ .../timer/modular/entity/SysTimers.java | 58 +++ .../timer/modular/mapper/SysTimersMapper.java | 16 + .../mapper/mapping/SysTimersMapper.xml | 5 + .../timer/modular/param/SysTimersParam.java | 54 ++ .../modular/service/SysTimersService.java | 102 ++++ .../service/impl/SysTimersServiceImpl.java | 193 +++++++ .../modular/tasks/SystemOutTaskRunner.java | 20 + kernel-d-timer/timer-sdk-hutool/README.md | 1 + kernel-d-timer/timer-sdk-hutool/pom.xml | 29 ++ .../hutool/HutoolTimerExeServiceImpl.java | 70 +++ .../timer-spring-boot-starter/README.md | 1 + .../timer-spring-boot-starter/pom.xml | 58 +++ .../starter/GunsTimerAutoConfiguration.java | 30 ++ .../main/resources/META-INF/spring.factories | 2 + kernel-d-validator/README.md | 1 + kernel-d-validator/pom.xml | 37 ++ kernel-d-validator/validator-api/README.md | 1 + kernel-d-validator/validator-api/pom.xml | 56 +++ .../roses/kernel/validator/BlackListApi.java | 53 ++ .../kernel/validator/CountValidatorApi.java | 28 ++ .../roses/kernel/validator/WhiteListApi.java | 53 ++ .../constants/ValidatorConstants.java | 56 +++ .../context/RequestGroupContext.java | 43 ++ .../context/RequestParamIdContext.java | 45 ++ .../exception/CountValidateException.java | 23 + .../exception/ParamValidateException.java | 23 + .../enums/CountValidateExceptionEnum.java | 42 ++ .../enums/ValidatorExceptionEnum.java | 47 ++ .../validator/pojo/UniqueValidateParam.java | 56 +++ .../kernel/validator/utils/ValidatorUtil.java | 96 ++++ .../validator/validators/date/DateValue.java | 53 ++ .../validators/date/DateValueValidator.java | 43 ++ .../validator/validators/flag/FlagValue.java | 40 ++ .../validators/flag/FlagValueValidator.java | 40 ++ .../validators/status/StatusValue.java | 41 ++ .../status/StatusValueValidator.java | 41 ++ .../validators/unique/TableUniqueValue.java | 66 +++ .../unique/TableUniqueValueValidator.java | 120 +++++ .../service/TableUniqueValueService.java | 125 +++++ .../validator-business-count/README.md | 1 + .../validator-business-count/pom.xml | 59 +++ .../modular/controller/package-info.java | 1 + .../modular/entity/package-info.java | 1 + .../validator/modular/enums/package-info.java | 1 + .../modular/mapper/package-info.java | 1 + .../validator/modular/param/package-info.java | 1 + .../modular/service/package-info.java | 1 + .../validator-sdk-black-white/README.md | 1 + .../validator-sdk-black-white/pom.xml | 44 ++ .../kemel/blackwhite/BlackListService.java | 44 ++ .../kemel/blackwhite/WhiteListService.java | 44 ++ .../cache/BlackListMemoryCache.java | 25 + .../blackwhite/cache/BlackListRedisCache.java | 25 + .../cache/WhiteListMemoryCache.java | 25 + .../blackwhite/cache/WhiteListRedisCache.java | 25 + .../validator-sdk-count/README.md | 1 + .../validator-sdk-count/pom.xml | 43 ++ .../kemel/count/DefaultCountValidator.java | 73 +++ .../cache/DefaultCountValidateCache.java | 25 + .../validator-spring-boot-starter/README.md | 1 + .../validator-spring-boot-starter/pom.xml | 65 +++ .../GunsValidatorAutoConfiguration.java | 69 +++ .../main/resources/META-INF/spring.factories | 2 + kernel-s-demo/README.md | 3 + kernel-s-demo/demo-api/README.md | 1 + kernel-s-demo/demo-api/pom.xml | 49 ++ .../kernel/demo/constants/DemoConstants.java | 21 + .../kernel/demo/exception/DemoException.java | 19 + .../exception/enums/DemoExceptionEnum.java | 37 ++ .../demo/expander/DemoConfigExpander.java | 24 + kernel-s-demo/demo-business/README.md | 1 + kernel-s-demo/demo-business/pom.xml | 36 ++ .../demo/DemoProfileSqlInterceptor.java | 59 +++ .../demo-spring-boot-starter/README.md | 1 + .../demo-spring-boot-starter/pom.xml | 51 ++ .../starter/GunsDemoAutoConfiguration.java | 30 ++ .../main/resources/META-INF/spring.factories | 2 + kernel-s-demo/pom.xml | 41 ++ kernel-s-dict/README.md | 1 + kernel-s-dict/dict-api/README.md | 3 + kernel-s-dict/dict-api/pom.xml | 42 ++ .../roses/kernel/dict/api/DictApi.java | 11 + .../dict/api/constants/DictConstants.java | 26 + .../kernel/dict/api/context/DictContext.java | 24 + .../dict/api/enums/DictTypeClassEnum.java | 30 ++ .../dict/api/exception/DictException.java | 23 + .../exception/enums/DictExceptionEnum.java | 67 +++ kernel-s-dict/dict-business/README.md | 3 + kernel-s-dict/dict-business/pom.xml | 59 +++ .../modular/controller/DictController.java | 142 ++++++ .../controller/DictTypeController.java | 120 +++++ .../kernel/dict/modular/entity/Dict.java | 93 ++++ .../kernel/dict/modular/entity/DictType.java | 72 +++ .../dict/modular/mapper/DictMapper.java | 40 ++ .../dict/modular/mapper/DictTypeMapper.java | 30 ++ .../modular/mapper/mapping/DictMapper.xml | 87 ++++ .../modular/mapper/mapping/DictTypeMapper.xml | 50 ++ .../dict/modular/pojo/TreeDictInfo.java | 67 +++ .../modular/pojo/request/DictRequest.java | 81 +++ .../modular/pojo/request/DictTypeRequest.java | 55 ++ .../dict/modular/service/DictService.java | 105 ++++ .../dict/modular/service/DictTypeService.java | 87 ++++ .../modular/service/impl/DictServiceImpl.java | 243 +++++++++ .../service/impl/DictTypeServiceImpl.java | 151 ++++++ .../dict-spring-boot-starter/README.md | 1 + .../dict-spring-boot-starter/pom.xml | 51 ++ .../starter/GunsDictAutoConfiguration.java | 14 + .../main/resources/META-INF/spring.factories | 2 + kernel-s-dict/pom.xml | 35 ++ kernel-s-system/README.md | 7 + kernel-s-system/_sql/system.sql | 431 ++++++++++++++++ kernel-s-system/pom.xml | 40 ++ kernel-s-system/system-api/README.md | 1 + kernel-s-system/system-api/pom.xml | 84 ++++ .../roses/kernel/system/AppServiceApi.java | 25 + .../roses/kernel/system/DataScopeApi.java | 25 + .../roses/kernel/system/MenuServiceApi.java | 33 ++ .../kernel/system/ResourceServiceApi.java | 46 ++ .../roses/kernel/system/RoleServiceApi.java | 65 +++ .../roses/kernel/system/SysEmployeeApi.java | 59 +++ .../roses/kernel/system/UserServiceApi.java | 76 +++ .../system/constants/SymbolConstant.java | 66 +++ .../system/constants/SystemConstants.java | 36 ++ .../kernel/system/enums/LinkOpenTypeEnum.java | 33 ++ .../kernel/system/enums/UserStatusEnum.java | 88 ++++ .../system/exception/DataScopeException.java | 24 + .../exception/SystemModularException.java | 23 + .../exception/enums/AppExceptionEnum.java | 71 +++ .../enums/DataScopeExceptionEnum.java | 47 ++ .../enums/EmployeeExceptionEnum.java | 47 ++ .../enums/OrganizationExceptionEnum.java | 52 ++ .../enums/PositionExceptionEnum.java | 42 ++ .../exception/enums/SysMenuExceptionEnum.java | 89 ++++ .../exception/enums/SysRoleExceptionEnum.java | 71 +++ .../exception/enums/SysUserExceptionEnum.java | 101 ++++ .../pojo/app/request/SysAppRequest.java | 58 +++ .../pojo/app/response/SysAppResponse.java | 67 +++ .../system/pojo/menu/SysMenuRequest.java | 142 ++++++ .../pojo/menu/tree/AntdBaseTreeNode.java | 62 +++ .../pojo/menu/tree/CommonBaseTreeNode.java | 52 ++ .../pojo/menu/tree/LoginMenuTreeNode.java | 87 ++++ .../pojo/menu/tree/MenuBaseTreeNode.java | 62 +++ .../pojo/organization/DataScopeResponse.java | 39 ++ .../pojo/organization/SysEmployeeRequest.java | 60 +++ .../organization/SysEmployeeResponse.java | 58 +++ .../organization/SysOrganizationRequest.java | 76 +++ .../pojo/organization/SysPositionRequest.java | 64 +++ .../resource/request/ResourceRequest.java | 38 ++ .../pojo/role/request/SysRoleRequest.java | 118 +++++ .../pojo/role/response/SysRoleResponse.java | 55 ++ .../system/pojo/user/UserLoginInfoDTO.java | 30 ++ .../kernel/system/util/DataScopeUtil.java | 121 +++++ kernel-s-system/system-business-app/README.md | 1 + kernel-s-system/system-business-app/pom.xml | 59 +++ .../modular/controller/SysAppController.java | 110 ++++ .../kernel/app/modular/entity/SysApp.java | 57 +++ .../app/modular/mapper/SysAppMapper.java | 37 ++ .../modular/mapper/mapping/SysAppMapper.xml | 5 + .../app/modular/service/SysAppService.java | 108 ++++ .../service/impl/SysAppServiceImpl.java | 215 ++++++++ .../system-business-menu/README.md | 1 + kernel-s-system/system-business-menu/pom.xml | 59 +++ .../modular/controller/SysMenuController.java | 120 +++++ .../kernel/menu/modular/entity/SysMenu.java | 147 ++++++ .../menu/modular/factory/MenuFactory.java | 74 +++ .../menu/modular/mapper/SysMenuMapper.java | 13 + .../modular/mapper/mapping/SysMenuMapper.xml | 5 + .../menu/modular/service/SysMenuService.java | 120 +++++ .../service/impl/SysMenuServiceImpl.java | 398 +++++++++++++++ .../system-business-organization/README.md | 1 + .../system-business-organization/pom.xml | 59 +++ .../controller/SysOrganizationController.java | 111 ++++ .../controller/SysPositionController.java | 111 ++++ .../organization/entity/SysEmployee.java | 57 +++ .../organization/entity/SysOrganization.java | 77 +++ .../organization/entity/SysPosition.java | 65 +++ .../mapper/SysEmployeeMapper.java | 14 + .../mapper/SysOrganizationMapper.java | 14 + .../mapper/SysPositionMapper.java | 14 + .../mapper/mapping/SysEmployeeMapper.xml | 19 + .../mapper/mapping/SysOrganizationMapper.xml | 22 + .../mapper/mapping/SysPositionMapper.xml | 20 + .../service/DataScopeService.java | 122 +++++ .../service/SysEmployeeService.java | 40 ++ .../service/SysOrganizationService.java | 106 ++++ .../service/SysPositionService.java | 94 ++++ .../service/impl/SysEmployeeServiceImpl.java | 212 ++++++++ .../impl/SysOrganizationServiceImpl.java | 352 +++++++++++++ .../service/impl/SysPositionServiceImpl.java | 176 +++++++ .../system-business-resource/README.md | 1 + .../system-business-resource/pom.xml | 67 +++ .../resource/modular/cache/ResourceCache.java | 49 ++ .../controller/ResourceController.java | 53 ++ .../resource/modular/entity/SysResource.java | 140 ++++++ .../modular/factory/ResourceFactory.java | 108 ++++ .../modular/mapper/SysResourceMapper.java | 14 + .../mapper/mapping/SysResourceMapper.xml | 28 ++ .../modular/service/SysResourceService.java | 49 ++ .../service/impl/SysResourceServiceImpl.java | 221 ++++++++ .../system-business-role/README.md | 3 + kernel-s-system/system-business-role/pom.xml | 59 +++ .../modular/controller/SysRoleController.java | 154 ++++++ .../kernel/role/modular/entity/SysRole.java | 94 ++++ .../role/modular/entity/SysRoleDataScope.java | 64 +++ .../role/modular/entity/SysRoleResource.java | 65 +++ .../mapper/SysRoleDataScopeMapper.java | 37 ++ .../role/modular/mapper/SysRoleMapper.java | 37 ++ .../modular/mapper/SysRoleMenuMapper.java | 37 ++ .../mapper/mapping/SysRoleDataScopeMapper.xml | 5 + .../modular/mapper/mapping/SysRoleMapper.xml | 5 + .../mapper/mapping/SysRoleMenuMapper.xml | 5 + .../service/SysRoleDataScopeService.java | 55 ++ .../service/SysRoleResourceService.java | 68 +++ .../role/modular/service/SysRoleService.java | 149 ++++++ .../impl/SysRoleDataScopeServiceImpl.java | 95 ++++ .../impl/SysRoleResourceServiceImpl.java | 88 ++++ .../service/impl/SysRoleServiceImpl.java | 388 ++++++++++++++ .../system-business-user/README.md | 1 + kernel-s-system/system-business-user/pom.xml | 81 +++ .../user/controller/SysUserController.java | 225 +++++++++ .../system/modular/user/entity/SysUser.java | 136 +++++ .../modular/user/entity/SysUserDataScope.java | 36 ++ .../modular/user/entity/SysUserRole.java | 36 ++ .../user/factory/LoginUserFactory.java | 129 +++++ .../modular/user/factory/SysUserFactory.java | 96 ++++ .../user/mapper/SysUserDataScopeMapper.java | 14 + .../modular/user/mapper/SysUserMapper.java | 28 ++ .../user/mapper/SysUserRoleMapper.java | 15 + .../mapper/mapping/SysUserDataScopeMapper.xml | 5 + .../user/mapper/mapping/SysUserMapper.xml | 45 ++ .../user/mapper/mapping/SysUserRoleMapper.xml | 5 + .../user/pojo/request/SysUserRequest.java | 246 +++++++++ .../user/pojo/response/SysUserResponse.java | 79 +++ .../user/service/SysUserDataScopeService.java | 44 ++ .../user/service/SysUserRoleService.java | 32 ++ .../modular/user/service/SysUserService.java | 173 +++++++ .../impl/SysUserDataScopeServiceImpl.java | 65 +++ .../service/impl/SysUserRoleServiceImpl.java | 65 +++ .../user/service/impl/SysUserServiceImpl.java | 475 ++++++++++++++++++ .../system-spring-boot-starter/README.md | 1 + .../system-spring-boot-starter/pom.xml | 86 ++++ pom.xml | 275 ++++++++++ 647 files changed, 35793 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 kernel-a-rule/README.md create mode 100644 kernel-a-rule/pom.xml create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/abstracts/AbstractExceptionEnum.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/abstracts/AbstractTreeNode.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/constants/RuleConstants.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/constants/TreeConstants.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/enums/SexEnum.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/enums/StatusEnum.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/enums/YesOrNotEnum.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/base/ServiceException.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/defaults/DefaultBusinessExceptionEnum.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/defaults/DefaultThirdExceptionEnum.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/defaults/DefaultUserExceptionEnum.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/http/ServletExceptionEnum.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/factory/DefaultTreeBuildFactory.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/factory/TreeNodeFactory.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/factory/base/AbstractTreeBuildFactory.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/dict/SimpleDict.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/request/BaseRequest.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/response/ErrorResponseData.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/response/ResponseData.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/response/SuccessResponseData.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/tree/DefaultTreeNode.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/util/AopTargetUtils.java create mode 100644 kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/util/HttpServletUtil.java create mode 100644 kernel-d-auth/README.md create mode 100644 kernel-d-auth/auth-api/README.md create mode 100644 kernel-d-auth/auth-api/pom.xml create mode 100644 kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/AuthServiceApi.java create mode 100644 kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/LoginUserApi.java create mode 100644 kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/PermissionServiceApi.java create mode 100644 kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/SessionManagerApi.java create mode 100644 kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/constants/AuthConstants.java create mode 100644 kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/context/LoginContext.java create mode 100644 kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/enums/DataScopeTypeEnum.java create mode 100644 kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/AuthException.java create mode 100644 kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/enums/AuthExceptionEnum.java create mode 100644 kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/expander/AuthConfigExpander.java create mode 100644 kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginRequest.java create mode 100644 kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginResponse.java create mode 100644 kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/login/LoginUser.java create mode 100644 kernel-d-auth/auth-sdk/README.md create mode 100644 kernel-d-auth/auth-sdk/pom.xml create mode 100644 kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java create mode 100644 kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/LoginUserImpl.java create mode 100644 kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/permission/PermissionServiceImpl.java create mode 100644 kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/session/MemoryCacheSessionManager.java create mode 100644 kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/session/RedisSessionManager.java create mode 100644 kernel-d-auth/auth-spring-boot-starter/README.md create mode 100644 kernel-d-auth/auth-spring-boot-starter/pom.xml create mode 100644 kernel-d-auth/auth-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/auth/starter/GunsAuthAutoConfiguration.java create mode 100644 kernel-d-auth/auth-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-d-auth/pom.xml create mode 100644 kernel-d-cache/README.md create mode 100644 kernel-d-cache/cache-api/README.md create mode 100644 kernel-d-cache/cache-api/pom.xml create mode 100644 kernel-d-cache/cache-api/src/main/java/cn/stylefeng/roses/kernel/cache/api/CacheOperatorApi.java create mode 100644 kernel-d-cache/cache-api/src/main/java/cn/stylefeng/roses/kernel/cache/api/constants/CacheConstants.java create mode 100644 kernel-d-cache/cache-sdk-memory/README.md create mode 100644 kernel-d-cache/cache-sdk-memory/pom.xml create mode 100644 kernel-d-cache/cache-sdk-memory/src/main/java/cn/stylefeng/roses/kernel/cache/AbstractMemoryCacheOperator.java create mode 100644 kernel-d-cache/cache-sdk-redis/README.md create mode 100644 kernel-d-cache/cache-sdk-redis/pom.xml create mode 100644 kernel-d-cache/cache-sdk-redis/src/main/java/cn/stylefeng/roses/kernel/cache/AbstractRedisCacheOperator.java create mode 100644 kernel-d-cache/pom.xml create mode 100644 kernel-d-config/README.md create mode 100644 kernel-d-config/config-api/README.md create mode 100644 kernel-d-config/config-api/pom.xml create mode 100644 kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/ConfigApi.java create mode 100644 kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/constants/ConfigConstants.java create mode 100644 kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/context/ConfigContext.java create mode 100644 kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/exception/ConfigException.java create mode 100644 kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/exception/enums/ConfigExceptionEnum.java create mode 100644 kernel-d-config/config-business/README.md create mode 100644 kernel-d-config/config-business/pom.xml create mode 100644 kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/controller/SysConfigController.java create mode 100644 kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/entity/SysConfig.java create mode 100644 kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/listener/ConfigInitListener.java create mode 100644 kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/mapper/SysConfigMapper.java create mode 100644 kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/mapper/mapping/SysConfigMapper.xml create mode 100644 kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/param/SysConfigParam.java create mode 100644 kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/service/SysConfigService.java create mode 100644 kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/service/impl/SysConfigServiceImpl.java create mode 100644 kernel-d-config/config-sdk-db/README.md create mode 100644 kernel-d-config/config-sdk-db/pom.xml create mode 100644 kernel-d-config/config-sdk-db/src/main/java/cn/stylefeng/roses/kernel/config/ConfigContainer.java create mode 100644 kernel-d-config/config-spring-boot-starter/README.md create mode 100644 kernel-d-config/config-spring-boot-starter/pom.xml create mode 100644 kernel-d-config/config-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/config/starter/GunsSysConfigAutoConfiguration.java create mode 100644 kernel-d-config/config-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-d-config/pom.xml create mode 100644 kernel-d-db/README.md create mode 100644 kernel-d-db/db-api/README.md create mode 100644 kernel-d-db/db-api/pom.xml create mode 100644 kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/DbOperatorApi.java create mode 100644 kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/constants/DbConstants.java create mode 100644 kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/constants/DbFieldConstants.java create mode 100644 kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/context/DbOperatorContext.java create mode 100644 kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/enums/DbTypeEnum.java create mode 100644 kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/exception/DaoException.java create mode 100644 kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/exception/enums/DaoExceptionEnum.java create mode 100644 kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/factory/DruidFactory.java create mode 100644 kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/factory/PageFactory.java create mode 100644 kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/factory/PageResultFactory.java create mode 100644 kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/pojo/druid/DruidProperties.java create mode 100644 kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/pojo/entity/BaseEntity.java create mode 100644 kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/pojo/page/PageResult.java create mode 100644 kernel-d-db/db-sdk-mp/README.md create mode 100644 kernel-d-db/db-sdk-mp/pom.xml create mode 100644 kernel-d-db/db-sdk-mp/src/main/java/cn/stylefeng/roses/kernel/db/mp/dbid/CustomDatabaseIdProvider.java create mode 100644 kernel-d-db/db-sdk-mp/src/main/java/cn/stylefeng/roses/kernel/db/mp/dboperator/DbOperatorImpl.java create mode 100644 kernel-d-db/db-sdk-mp/src/main/java/cn/stylefeng/roses/kernel/db/mp/fieldfill/CustomMetaObjectHandler.java create mode 100644 kernel-d-db/db-spring-boot-starter/README.md create mode 100644 kernel-d-db/db-spring-boot-starter/pom.xml create mode 100644 kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/GunsDataSourceAutoConfiguration.java create mode 100644 kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/GunsDruidPropertiesAutoConfiguration.java create mode 100644 kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/GunsMyBatisPlusAutoConfiguration.java create mode 100644 kernel-d-db/db-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-d-db/pom.xml create mode 100644 kernel-d-ds-container/README.md create mode 100644 kernel-d-ds-container/ds-container-api/README.md create mode 100644 kernel-d-ds-container/ds-container-api/pom.xml create mode 100644 kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/annotation/DataSource.java create mode 100644 kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/constants/DatasourceContainerConstants.java create mode 100644 kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/context/CurrentDataSourceContext.java create mode 100644 kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/exception/DatasourceContainerException.java create mode 100644 kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/exception/enums/DatasourceContainerExceptionEnum.java create mode 100644 kernel-d-ds-container/ds-container-business/README.md create mode 100644 kernel-d-ds-container/ds-container-business/pom.xml create mode 100644 kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/controller/DatabaseInfoController.java create mode 100644 kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/entity/DatabaseInfo.java create mode 100644 kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/factory/DruidFactory.java create mode 100644 kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/mapper/DatabaseInfoMapper.java create mode 100644 kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/mapper/mapping/DatabaseInfoMapper.xml create mode 100644 kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/pojo/DatabaseInfoParam.java create mode 100644 kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/service/DatabaseInfoService.java create mode 100644 kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/service/impl/DatabaseInfoServiceImpl.java create mode 100644 kernel-d-ds-container/ds-container-sdk/README.md create mode 100644 kernel-d-ds-container/ds-container-sdk/pom.xml create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/AbstractRoutingDataSource.java create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/DynamicDataSource.java create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/aop/MultiSourceExchangeAop.java create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/context/DataSourceContext.java create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/listener/DataSourceInitListener.java create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/DataBaseInfoPersistence.java create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/AbstractSql.java create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/AddDatabaseInfoSql.java create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/CreateDatabaseSql.java create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/DatabaseListSql.java create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/DeleteDatabaseInfoSql.java create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/DropDatabaseSql.java create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/TableFieldListSql.java create mode 100644 kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/TableListSql.java create mode 100644 kernel-d-ds-container/ds-container-spring-boot-starter/README.md create mode 100644 kernel-d-ds-container/ds-container-spring-boot-starter/pom.xml create mode 100644 kernel-d-ds-container/ds-container-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/dsctn/starter/GunsDataSourceContainerAutoConfiguration.java create mode 100644 kernel-d-ds-container/ds-container-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-d-ds-container/pom.xml create mode 100644 kernel-d-email/README.md create mode 100644 kernel-d-email/email-api/README.md create mode 100644 kernel-d-email/email-api/pom.xml create mode 100644 kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/MailSenderApi.java create mode 100644 kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/constants/MailConstants.java create mode 100644 kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/context/MailSenderContext.java create mode 100644 kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/exception/MailException.java create mode 100644 kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/exception/enums/EmailExceptionEnum.java create mode 100644 kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/expander/EmailConfigExpander.java create mode 100644 kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/pojo/SendMailParam.java create mode 100644 kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/pojo/aliyun/AliyunMailSenderProperties.java create mode 100644 kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/pojo/aliyun/AliyunSendMailParam.java create mode 100644 kernel-d-email/email-sdk-aliyun/README.md create mode 100644 kernel-d-email/email-sdk-aliyun/pom.xml create mode 100644 kernel-d-email/email-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/email/aliyun/AliyunMailSender.java create mode 100644 kernel-d-email/email-sdk-java/README.md create mode 100644 kernel-d-email/email-sdk-java/pom.xml create mode 100644 kernel-d-email/email-sdk-java/src/main/java/cn/stylefeng/roses/kernel/email/JavaMailSender.java create mode 100644 kernel-d-email/email-spring-boot-starter/README.md create mode 100644 kernel-d-email/email-spring-boot-starter/pom.xml create mode 100644 kernel-d-email/email-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/email/starter/GunsEmailAutoConfiguration.java create mode 100644 kernel-d-email/email-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-d-email/pom.xml create mode 100644 kernel-d-file/README.md create mode 100644 kernel-d-file/file-api/README.md create mode 100644 kernel-d-file/file-api/pom.xml create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/FileInfoApi.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/FileOperatorApi.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/constants/FileConstants.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/context/FileContext.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/enums/BucketAuthEnum.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/enums/FileLocationEnum.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/exception/FileException.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/exception/enums/FileExceptionEnum.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/expander/FileConfigExpander.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/AliyunOssProperties.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/LocalFileProperties.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/MinIoProperties.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/TenCosProperties.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/request/SysFileInfoRequest.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/response/SysFileInfoResponse.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/util/DownloadUtil.java create mode 100644 kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/util/PicFileTypeUtil.java create mode 100644 kernel-d-file/file-business/README.md create mode 100644 kernel-d-file/file-business/pom.xml create mode 100644 kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/controller/SysFileInfoController.java create mode 100644 kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/entity/SysFileInfo.java create mode 100644 kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/mapper/SysFileInfoMapper.java create mode 100644 kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/mapper/mapping/SysFileInfoMapper.xml create mode 100644 kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/service/SysFileInfoService.java create mode 100644 kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/service/impl/SysFileInfoServiceImpl.java create mode 100644 kernel-d-file/file-sdk-aliyun/README.md create mode 100644 kernel-d-file/file-sdk-aliyun/pom.xml create mode 100644 kernel-d-file/file-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/file/aliyun/AliyunFileOperator.java create mode 100644 kernel-d-file/file-sdk-local/README.md create mode 100644 kernel-d-file/file-sdk-local/pom.xml create mode 100644 kernel-d-file/file-sdk-local/src/main/java/cn/stylefeng/roses/kernel/file/local/LocalFileOperator.java create mode 100644 kernel-d-file/file-sdk-minio/README.md create mode 100644 kernel-d-file/file-sdk-minio/pom.xml create mode 100644 kernel-d-file/file-sdk-minio/src/main/java/cn/stylefeng/roses/kernel/file/minio/MinIoFileOperator.java create mode 100644 kernel-d-file/file-sdk-tencent/README.md create mode 100644 kernel-d-file/file-sdk-tencent/pom.xml create mode 100644 kernel-d-file/file-sdk-tencent/src/main/java/cn/stylefeng/roses/kernel/file/tencent/TenFileOperator.java create mode 100644 kernel-d-file/file-spring-boot-starter/README.md create mode 100644 kernel-d-file/file-spring-boot-starter/pom.xml create mode 100644 kernel-d-file/file-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/file/starter/GunsFileAutoConfiguration.java create mode 100644 kernel-d-file/file-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-d-file/pom.xml create mode 100644 kernel-d-jwt/README.md create mode 100644 kernel-d-jwt/jwt-api/README.md create mode 100644 kernel-d-jwt/jwt-api/pom.xml create mode 100644 kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/JwtApi.java create mode 100644 kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/constants/JwtConstants.java create mode 100644 kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/context/JwtContext.java create mode 100644 kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/exception/JwtException.java create mode 100644 kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/exception/enums/JwtExceptionEnum.java create mode 100644 kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/expander/JwtConfigExpander.java create mode 100644 kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/pojo/config/JwtConfig.java create mode 100644 kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/pojo/payload/DefaultJwtPayload.java create mode 100644 kernel-d-jwt/jwt-sdk/README.md create mode 100644 kernel-d-jwt/jwt-sdk/pom.xml create mode 100644 kernel-d-jwt/jwt-sdk/src/main/java/cn/stylefeng/roses/kernel/jwt/JwtTokenOperator.java create mode 100644 kernel-d-jwt/jwt-spring-boot-starter/README.md create mode 100644 kernel-d-jwt/jwt-spring-boot-starter/pom.xml create mode 100644 kernel-d-jwt/jwt-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/jwt/starter/GunsJwtAutoConfiguration.java create mode 100644 kernel-d-jwt/jwt-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-d-jwt/pom.xml create mode 100644 kernel-d-log/README.md create mode 100644 kernel-d-log/log-api/README.md create mode 100644 kernel-d-log/log-api/pom.xml create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/LogManagerApi.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/LogRecordApi.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/constants/LogConstants.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/constants/LogFileConstants.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/context/LogRecordContext.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/context/ServerInfoContext.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/exception/LogException.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/exception/enums/LogExceptionEnum.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/expander/LogConfigExpander.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/LogRecordFactory.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/appender/AuthedLogAppender.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/appender/HttpLogAppender.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/appender/ParamsLogAppender.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/pojo/manage/LogManagerParam.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/pojo/record/LogRecordDTO.java create mode 100644 kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/threadpool/LogManagerThreadPool.java create mode 100644 kernel-d-log/log-business-manage/README.md create mode 100644 kernel-d-log/log-business-manage/pom.xml create mode 100644 kernel-d-log/log-business-manage/src/main/java/cn/stylefeng/roses/kernel/log/modular/manage/controller/LogManagerController.java create mode 100644 kernel-d-log/log-business-requestapi/README.md create mode 100644 kernel-d-log/log-business-requestapi/pom.xml create mode 100644 kernel-d-log/log-business-requestapi/src/main/java/cn/stylefeng/roses/kernel/log/modular/requestapi/aop/RequestApiLogRecordAop.java create mode 100644 kernel-d-log/log-sdk-db/README.md create mode 100644 kernel-d-log/log-sdk-db/pom.xml create mode 100644 kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/DbLogManagerServiceImpl.java create mode 100644 kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/DbLogRecordServiceImpl.java create mode 100644 kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/entity/SysLog.java create mode 100644 kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/mapper/SysLogMapper.java create mode 100644 kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/mapper/mapping/SysLogMapper.xml create mode 100644 kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/service/SysLogService.java create mode 100644 kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/service/impl/SysLogServiceImpl.java create mode 100644 kernel-d-log/log-sdk-file/README.md create mode 100644 kernel-d-log/log-sdk-file/pom.xml create mode 100644 kernel-d-log/log-sdk-file/src/main/java/cn/stylefeng/roses/kernel/log/file/FileLogManagerServiceImpl.java create mode 100644 kernel-d-log/log-sdk-file/src/main/java/cn/stylefeng/roses/kernel/log/file/FileLogRecordServiceImpl.java create mode 100644 kernel-d-log/log-spring-boot-starter/README.md create mode 100644 kernel-d-log/log-spring-boot-starter/pom.xml create mode 100644 kernel-d-log/log-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/log/starter/GunsLogAutoConfiguration.java create mode 100644 kernel-d-log/log-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-d-log/pom.xml create mode 100644 kernel-d-office/README.md create mode 100644 kernel-d-office/office-api/README.md create mode 100644 kernel-d-office/office-api/pom.xml create mode 100644 kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/OfficeExcelApi.java create mode 100644 kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/constants/OfficeConstants.java create mode 100644 kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/exception/OfficeException.java create mode 100644 kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/exception/enums/OfficeExceptionEnum.java create mode 100644 kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/pojo/report/ExcelExportParam.java create mode 100644 kernel-d-office/office-sdk-excel/README.md create mode 100644 kernel-d-office/office-sdk-excel/pom.xml create mode 100644 kernel-d-office/office-sdk-excel/src/main/java/cn/stylefeng/roses/kernel/office/excel/OfficeExcel.java create mode 100644 kernel-d-office/office-sdk-excel/src/main/java/cn/stylefeng/roses/kernel/office/excel/listener/SimpleDataListener.java create mode 100644 kernel-d-office/office-spring-boot-starter/README.md create mode 100644 kernel-d-office/office-spring-boot-starter/pom.xml create mode 100644 kernel-d-office/pom.xml create mode 100644 kernel-d-pinyin/README.md create mode 100644 kernel-d-pinyin/pinyin-api/README.md create mode 100644 kernel-d-pinyin/pinyin-api/pom.xml create mode 100644 kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/PinYinApi.java create mode 100644 kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/constants/PinyinConstants.java create mode 100644 kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/context/PinyinContext.java create mode 100644 kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/exception/PinyinException.java create mode 100644 kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/exception/enums/PinyinExceptionEnum.java create mode 100644 kernel-d-pinyin/pinyin-sdk-pinyin4j/README.md create mode 100644 kernel-d-pinyin/pinyin-sdk-pinyin4j/pom.xml create mode 100644 kernel-d-pinyin/pinyin-sdk-pinyin4j/src/main/java/cn/stylefeng/roses/kernel/pinyin/PinyinServiceImpl.java create mode 100644 kernel-d-pinyin/pinyin-spring-boot-starter/README.md create mode 100644 kernel-d-pinyin/pinyin-spring-boot-starter/pom.xml create mode 100644 kernel-d-pinyin/pinyin-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/pinyin/starter/GunsPinyinAutoConfiguration.java create mode 100644 kernel-d-pinyin/pinyin-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-d-pinyin/pom.xml create mode 100644 kernel-d-scanner/README.md create mode 100644 kernel-d-scanner/pom.xml create mode 100644 kernel-d-scanner/scanner-api/README.md create mode 100644 kernel-d-scanner/scanner-api/pom.xml create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/ResourceCollectorApi.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/ResourceReportApi.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/ApiResource.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/GetResource.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/PostResource.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/field/ChineseDescription.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/constants/ScannerConstants.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/exception/ScannerException.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/exception/enums/ScannerExceptionEnum.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/holder/InitScanFlagHolder.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/holder/IpAddrHolder.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/FieldDescription.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/ReportResourceParam.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/ResourceDefinition.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/ResourceUrlParam.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/UserResourceParam.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/scanner/ScannerProperties.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/util/ClassReflectUtil.java create mode 100644 kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/util/MethodReflectUtil.java create mode 100644 kernel-d-scanner/scanner-sdk-scanner/README.md create mode 100644 kernel-d-scanner/scanner-sdk-scanner/pom.xml create mode 100644 kernel-d-scanner/scanner-sdk-scanner/src/main/java/cn/stylefeng/roses/kernel/resource/scanner/ApiResourceScanner.java create mode 100644 kernel-d-scanner/scanner-sdk-scanner/src/main/java/cn/stylefeng/roses/kernel/resource/scanner/DefaultResourceCollector.java create mode 100644 kernel-d-scanner/scanner-sdk-scanner/src/main/java/cn/stylefeng/roses/kernel/resource/scanner/ResourceReportListener.java create mode 100644 kernel-d-scanner/scanner-spring-boot-starter/README.md create mode 100644 kernel-d-scanner/scanner-spring-boot-starter/pom.xml create mode 100644 kernel-d-scanner/scanner-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/resource/starter/GunsResourceAutoConfiguration.java create mode 100644 kernel-d-scanner/scanner-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-d-sms/README.md create mode 100644 kernel-d-sms/pom.xml create mode 100644 kernel-d-sms/sms-api/README.md create mode 100644 kernel-d-sms/sms-api/pom.xml create mode 100644 kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/SmsSenderApi.java create mode 100644 kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/constants/SmsConstants.java create mode 100644 kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/context/SmsContext.java create mode 100644 kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/exception/SmsException.java create mode 100644 kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/exception/enums/SmsExceptionEnum.java create mode 100644 kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/expander/SmsConfigExpander.java create mode 100644 kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/pojo/AliyunSmsProperties.java create mode 100644 kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/pojo/TencentSmsProperties.java create mode 100644 kernel-d-sms/sms-business-validation/README.md create mode 100644 kernel-d-sms/sms-business-validation/pom.xml create mode 100644 kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/controller/SmsSenderController.java create mode 100644 kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/entity/SysSms.java create mode 100644 kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/enums/SmsSendSourceEnum.java create mode 100644 kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/enums/SmsSendStatusEnum.java create mode 100644 kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/enums/SmsTypeEnum.java create mode 100644 kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/mapper/SysSmsMapper.java create mode 100644 kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/mapper/mapping/SysSmsMapper.xml create mode 100644 kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/param/SysSmsInfoParam.java create mode 100644 kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/param/SysSmsSendParam.java create mode 100644 kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/param/SysSmsVerifyParam.java create mode 100644 kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/service/SysSmsInfoService.java create mode 100644 kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/service/impl/SysSmsInfoServiceImpl.java create mode 100644 kernel-d-sms/sms-sdk-aliyun/README.md create mode 100644 kernel-d-sms/sms-sdk-aliyun/pom.xml create mode 100644 kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/AliyunSmsSender.java create mode 100644 kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/enums/AliyunSmsResultEnum.java create mode 100644 kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/msign/MultiSignManager.java create mode 100644 kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/msign/impl/MapBasedMultiSignManager.java create mode 100644 kernel-d-sms/sms-sdk-tencent/README.md create mode 100644 kernel-d-sms/sms-sdk-tencent/pom.xml create mode 100644 kernel-d-sms/sms-sdk-tencent/src/main/java/cn/stylefeng/roses/kernel/sms/tencent/TencentSmsSender.java create mode 100644 kernel-d-sms/sms-spring-boot-starter/README.md create mode 100644 kernel-d-sms/sms-spring-boot-starter/pom.xml create mode 100644 kernel-d-sms/sms-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/sms/starter/GunsSmsAutoConfiguration.java create mode 100644 kernel-d-sms/sms-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-d-timer/README.md create mode 100644 kernel-d-timer/pom.xml create mode 100644 kernel-d-timer/timer-api/README.md create mode 100644 kernel-d-timer/timer-api/pom.xml create mode 100644 kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/TimerAction.java create mode 100644 kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/TimerExeService.java create mode 100644 kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/constants/TimerConstants.java create mode 100644 kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/enums/TimerJobStatusEnum.java create mode 100644 kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/exception/TimerException.java create mode 100644 kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/exception/enums/TimerExceptionEnum.java create mode 100644 kernel-d-timer/timer-business/README.md create mode 100644 kernel-d-timer/timer-business/pom.xml create mode 100644 kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/controller/SysTimersController.java create mode 100644 kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/entity/SysTimers.java create mode 100644 kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/mapper/SysTimersMapper.java create mode 100644 kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/mapper/mapping/SysTimersMapper.xml create mode 100644 kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/param/SysTimersParam.java create mode 100644 kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/service/SysTimersService.java create mode 100644 kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/service/impl/SysTimersServiceImpl.java create mode 100644 kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/tasks/SystemOutTaskRunner.java create mode 100644 kernel-d-timer/timer-sdk-hutool/README.md create mode 100644 kernel-d-timer/timer-sdk-hutool/pom.xml create mode 100644 kernel-d-timer/timer-sdk-hutool/src/main/java/cn/stylefeng/roses/kernel/hutool/HutoolTimerExeServiceImpl.java create mode 100644 kernel-d-timer/timer-spring-boot-starter/README.md create mode 100644 kernel-d-timer/timer-spring-boot-starter/pom.xml create mode 100644 kernel-d-timer/timer-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/timer/starter/GunsTimerAutoConfiguration.java create mode 100644 kernel-d-timer/timer-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-d-validator/README.md create mode 100644 kernel-d-validator/pom.xml create mode 100644 kernel-d-validator/validator-api/README.md create mode 100644 kernel-d-validator/validator-api/pom.xml create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/BlackListApi.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/CountValidatorApi.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/WhiteListApi.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/constants/ValidatorConstants.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/context/RequestGroupContext.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/context/RequestParamIdContext.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/CountValidateException.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/ParamValidateException.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/enums/CountValidateExceptionEnum.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/enums/ValidatorExceptionEnum.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/pojo/UniqueValidateParam.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/utils/ValidatorUtil.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/date/DateValue.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/date/DateValueValidator.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/flag/FlagValue.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/flag/FlagValueValidator.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/status/StatusValue.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/status/StatusValueValidator.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/unique/TableUniqueValue.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/unique/TableUniqueValueValidator.java create mode 100644 kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/unique/service/TableUniqueValueService.java create mode 100644 kernel-d-validator/validator-business-count/README.md create mode 100644 kernel-d-validator/validator-business-count/pom.xml create mode 100644 kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/controller/package-info.java create mode 100644 kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/entity/package-info.java create mode 100644 kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/enums/package-info.java create mode 100644 kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/mapper/package-info.java create mode 100644 kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/param/package-info.java create mode 100644 kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/service/package-info.java create mode 100644 kernel-d-validator/validator-sdk-black-white/README.md create mode 100644 kernel-d-validator/validator-sdk-black-white/pom.xml create mode 100644 kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/BlackListService.java create mode 100644 kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/WhiteListService.java create mode 100644 kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/BlackListMemoryCache.java create mode 100644 kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/BlackListRedisCache.java create mode 100644 kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/WhiteListMemoryCache.java create mode 100644 kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/WhiteListRedisCache.java create mode 100644 kernel-d-validator/validator-sdk-count/README.md create mode 100644 kernel-d-validator/validator-sdk-count/pom.xml create mode 100644 kernel-d-validator/validator-sdk-count/src/main/java/cn/stylefeng/roses/kemel/count/DefaultCountValidator.java create mode 100644 kernel-d-validator/validator-sdk-count/src/main/java/cn/stylefeng/roses/kemel/count/cache/DefaultCountValidateCache.java create mode 100644 kernel-d-validator/validator-spring-boot-starter/README.md create mode 100644 kernel-d-validator/validator-spring-boot-starter/pom.xml create mode 100644 kernel-d-validator/validator-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/validator/starter/GunsValidatorAutoConfiguration.java create mode 100644 kernel-d-validator/validator-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-s-demo/README.md create mode 100644 kernel-s-demo/demo-api/README.md create mode 100644 kernel-s-demo/demo-api/pom.xml create mode 100644 kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/constants/DemoConstants.java create mode 100644 kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/exception/DemoException.java create mode 100644 kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/exception/enums/DemoExceptionEnum.java create mode 100644 kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/expander/DemoConfigExpander.java create mode 100644 kernel-s-demo/demo-business/README.md create mode 100644 kernel-s-demo/demo-business/pom.xml create mode 100644 kernel-s-demo/demo-business/src/main/java/cn/stylefeng/roses/kernel/demo/DemoProfileSqlInterceptor.java create mode 100644 kernel-s-demo/demo-spring-boot-starter/README.md create mode 100644 kernel-s-demo/demo-spring-boot-starter/pom.xml create mode 100644 kernel-s-demo/demo-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/demo/starter/GunsDemoAutoConfiguration.java create mode 100644 kernel-s-demo/demo-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-s-demo/pom.xml create mode 100644 kernel-s-dict/README.md create mode 100644 kernel-s-dict/dict-api/README.md create mode 100644 kernel-s-dict/dict-api/pom.xml create mode 100644 kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/DictApi.java create mode 100644 kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/constants/DictConstants.java create mode 100644 kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/context/DictContext.java create mode 100644 kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/enums/DictTypeClassEnum.java create mode 100644 kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/exception/DictException.java create mode 100644 kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/exception/enums/DictExceptionEnum.java create mode 100644 kernel-s-dict/dict-business/README.md create mode 100644 kernel-s-dict/dict-business/pom.xml create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/controller/DictController.java create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/controller/DictTypeController.java create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/entity/Dict.java create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/entity/DictType.java create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/DictMapper.java create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/DictTypeMapper.java create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/mapping/DictMapper.xml create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/mapping/DictTypeMapper.xml create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/pojo/TreeDictInfo.java create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/pojo/request/DictRequest.java create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/pojo/request/DictTypeRequest.java create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/DictService.java create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/DictTypeService.java create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/impl/DictServiceImpl.java create mode 100644 kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/impl/DictTypeServiceImpl.java create mode 100644 kernel-s-dict/dict-spring-boot-starter/README.md create mode 100644 kernel-s-dict/dict-spring-boot-starter/pom.xml create mode 100644 kernel-s-dict/dict-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/dict/starter/GunsDictAutoConfiguration.java create mode 100644 kernel-s-dict/dict-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 kernel-s-dict/pom.xml create mode 100644 kernel-s-system/README.md create mode 100644 kernel-s-system/_sql/system.sql create mode 100644 kernel-s-system/pom.xml create mode 100644 kernel-s-system/system-api/README.md create mode 100644 kernel-s-system/system-api/pom.xml create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/AppServiceApi.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/DataScopeApi.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/MenuServiceApi.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/ResourceServiceApi.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/RoleServiceApi.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/SysEmployeeApi.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/UserServiceApi.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/constants/SymbolConstant.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/constants/SystemConstants.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/enums/LinkOpenTypeEnum.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/enums/UserStatusEnum.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/DataScopeException.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/SystemModularException.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/AppExceptionEnum.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/DataScopeExceptionEnum.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/EmployeeExceptionEnum.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/OrganizationExceptionEnum.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/PositionExceptionEnum.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/SysMenuExceptionEnum.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/SysRoleExceptionEnum.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/SysUserExceptionEnum.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/app/request/SysAppRequest.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/app/response/SysAppResponse.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/SysMenuRequest.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/AntdBaseTreeNode.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/CommonBaseTreeNode.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/LoginMenuTreeNode.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/MenuBaseTreeNode.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/DataScopeResponse.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysEmployeeRequest.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysEmployeeResponse.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysOrganizationRequest.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysPositionRequest.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/resource/request/ResourceRequest.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/role/request/SysRoleRequest.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/role/response/SysRoleResponse.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/user/UserLoginInfoDTO.java create mode 100644 kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/util/DataScopeUtil.java create mode 100644 kernel-s-system/system-business-app/README.md create mode 100644 kernel-s-system/system-business-app/pom.xml create mode 100644 kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/controller/SysAppController.java create mode 100644 kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/entity/SysApp.java create mode 100644 kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/mapper/SysAppMapper.java create mode 100644 kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/mapper/mapping/SysAppMapper.xml create mode 100644 kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/service/SysAppService.java create mode 100644 kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/service/impl/SysAppServiceImpl.java create mode 100644 kernel-s-system/system-business-menu/README.md create mode 100644 kernel-s-system/system-business-menu/pom.xml create mode 100644 kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/controller/SysMenuController.java create mode 100644 kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/entity/SysMenu.java create mode 100644 kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/factory/MenuFactory.java create mode 100644 kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/mapper/SysMenuMapper.java create mode 100644 kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/mapper/mapping/SysMenuMapper.xml create mode 100644 kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/service/SysMenuService.java create mode 100644 kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/service/impl/SysMenuServiceImpl.java create mode 100644 kernel-s-system/system-business-organization/README.md create mode 100644 kernel-s-system/system-business-organization/pom.xml create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/controller/SysOrganizationController.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/controller/SysPositionController.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/entity/SysEmployee.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/entity/SysOrganization.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/entity/SysPosition.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/SysEmployeeMapper.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/SysOrganizationMapper.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/SysPositionMapper.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/mapping/SysEmployeeMapper.xml create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/mapping/SysOrganizationMapper.xml create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/mapping/SysPositionMapper.xml create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/DataScopeService.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/SysEmployeeService.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/SysOrganizationService.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/SysPositionService.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/impl/SysEmployeeServiceImpl.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/impl/SysOrganizationServiceImpl.java create mode 100644 kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/impl/SysPositionServiceImpl.java create mode 100644 kernel-s-system/system-business-resource/README.md create mode 100644 kernel-s-system/system-business-resource/pom.xml create mode 100644 kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/cache/ResourceCache.java create mode 100644 kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/controller/ResourceController.java create mode 100644 kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/entity/SysResource.java create mode 100644 kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/factory/ResourceFactory.java create mode 100644 kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/mapper/SysResourceMapper.java create mode 100644 kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/mapper/mapping/SysResourceMapper.xml create mode 100644 kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/service/SysResourceService.java create mode 100644 kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/service/impl/SysResourceServiceImpl.java create mode 100644 kernel-s-system/system-business-role/README.md create mode 100644 kernel-s-system/system-business-role/pom.xml create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/controller/SysRoleController.java create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/entity/SysRole.java create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/entity/SysRoleDataScope.java create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/entity/SysRoleResource.java create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/SysRoleDataScopeMapper.java create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/SysRoleMapper.java create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/SysRoleMenuMapper.java create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/mapping/SysRoleDataScopeMapper.xml create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/mapping/SysRoleMapper.xml create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/mapping/SysRoleMenuMapper.xml create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/SysRoleDataScopeService.java create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/SysRoleResourceService.java create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/SysRoleService.java create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/impl/SysRoleDataScopeServiceImpl.java create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/impl/SysRoleResourceServiceImpl.java create mode 100644 kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/impl/SysRoleServiceImpl.java create mode 100644 kernel-s-system/system-business-user/README.md create mode 100644 kernel-s-system/system-business-user/pom.xml create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/controller/SysUserController.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUser.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUserDataScope.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUserRole.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/factory/LoginUserFactory.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/factory/SysUserFactory.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/SysUserDataScopeMapper.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/SysUserMapper.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/SysUserRoleMapper.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/mapping/SysUserDataScopeMapper.xml create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/mapping/SysUserMapper.xml create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/mapping/SysUserRoleMapper.xml create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/pojo/request/SysUserRequest.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/pojo/response/SysUserResponse.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/SysUserDataScopeService.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/SysUserRoleService.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/SysUserService.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/impl/SysUserDataScopeServiceImpl.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/impl/SysUserRoleServiceImpl.java create mode 100644 kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/impl/SysUserServiceImpl.java create mode 100644 kernel-s-system/system-spring-boot-starter/README.md create mode 100644 kernel-s-system/system-spring-boot-starter/pom.xml create mode 100644 pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..4cf5330ff --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +*.class + +# Package Files # +*.jar +*.war +*.ear +target/ + +# eclipse +.settings/ +.classpath +.project +logs/ + +# idea +.idea/ +*.iml + +*velocity.log* + +### STS ### +.apt_generated +.factorypath +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +*.log +tmp/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..6994840cd --- /dev/null +++ b/README.md @@ -0,0 +1,131 @@ +## Roses核心包 + +基于《Java开发手册(嵩山版)》 + +### 规则1 模块的分类(a类,d类,o类,s类,p类) + +> a类排名第一,Advanced,为全模块的规则,本公司所有的代码都需要遵守的规则,包含枚举,异常,基础类等 + +> d类排名第二,Development,给开发人员用的快速开发工具,方便快速开发,例如日志,邮件,短信,缓存等 + +> o类排名第三,Operations,偏运维类的封装,例如监控,调用链记录,内网转发模块 + +> s类排名第四,Service,偏应用功能的封装,例如用户管理,角色管理,公司管理,每个模块是一个独立的业务 + +> p类排名第五,Pattern,设计模式或业务解决方案,例如高并发的解决方案,海量数据存储方案等 + +### 规则2 模块的建设标准 + +> 2.1 模块建立的基本思想是封装重用的代码,提高开发效率 + +> 2.2 由团队核心成员评估批准后进行模块的编写,模块的编写要遵守第3条规定 + +### 规则3 模块设计思想 + +> 3.1 每个模块内部分三类子模块 + +分别是api、sdk、business,api为对其他模块暴露的接口,sdk是对核心功能的封装,business是带业务逻辑的封装 + +以短信模块kernel-d-sms为例,sms-api模块是接口模块,是短信功能提供的所有接口。 + +sms-sdk-aliyun模块是阿里云短信的sdk封装。 + +sms-sdk-tencent模块是腾讯云短信的sdk封装。 + +sms-business-validation模块是带短信验证功能(业务)的模块。 + +api、sdk、business为三类模块,不是三个,一般api模块仅一个,sdk和business类模块可以无限拓展。 + +> 3.2 依赖接口不依赖实现 + +模块与模块之间的调用,通过api模块来调用(例如sms-api),而不直接依赖他的实现(sms-sdk或sms-business),具体的实现由business模块决定或者由具体项目决定。 + +> 3.3 每个模块要详细编写readme + +每个kernel模块要编写对应的readme文档 + +每个kernel的子模块也要写清楚readme文档 + +> 3.4 所有api的实现都装入spring容器中,使用api时通过@Resource注入api + +同一个项目,一个api的实现可以有两个,需要通过@Resource(name = "xxx")指定资源的名字。 + +### 规则4 模块中任何类均可拓展 + +利用@Primary注解来替换已经装载的spring容器中的bean + +## 规则5 每个模块要有一个常量类 + +常量类用来存放模块名称和异常枚举的步进值,如果本模块异常较多,可以存放多个步进值 + +```java +public interface RuleConstants { + + /** + * 规则模块的名称 + */ + String RULE_MODULE_NAME = "kernel-a-rule"; + + /** + * 异常枚举的步进值 + */ + String RULE_EXCEPTION_STEP_CODE = "00"; + +} +``` + +## 规则6 每个模块要有一个异常类 + +异常类要集成ServiceException + +```java +public class DaoException extends ServiceException { + + public DaoException(AbstractExceptionEnum exception) { + super(DbConstants.DB_MODULE_NAME, exception); + } + +} +``` + +## 规则7 强依赖 + +项目基于spring boot架构,所以对spring boot强依赖,另外对hutool工具类,lombok,fastjson强依赖,其他框架不强依赖 + +## 规则8 expander包是对配置表的拓展 + +kernel-d-config模块只负责对系统配置的初始化,新增,删除等操作,不进行对某个具体配置的维护,各个模块需要配置拓展属性时,在各个模块的api模块建立expander包维护 + +## 规则9 business可以依赖sdk层,sdk层可依赖api层,反之不行 + +## 规则10 高模块可依赖低模块的api,反之不行 + +s类的api模块可以依赖d类的api,反之不行,防止出现互相依赖(循环依赖)的情况 + +## 规则11 Bean的装配,尽量在类的构造函数,不要在类的内部用@Resource或者@Autowired + +构造函数装配更灵活,如果直接用@Resource则会交给spring去装配,spring会去找到容器中的相关bean,不如手动的灵活 + +多出现在装配的是接口的情况,如果接口有多个实现,很明显用构造函数去装配更合适 + +## 规则12 pojo的分包结构 + +pojo下可以分为request(控制器层请求参数的封装),response(控制器层响应参数的封装),param(其他类下参数的封装) + +其中request包下的类以Request结尾,response包下的类以Response结尾,param包下的类以Param结尾 + +request包下的类一般会加上参数校验注解,参数校验用的hibernate validator注解 + +一般情况,直接用实体返回,减少一些pojo的书写,复杂的返回对象还是要单独封装pojo + +## 规则13 表的设计 + +表名不要用缩写,用全拼单词 + +排序字段用decimal带两位小数点,这样往里边插入数据的时候,不用改别人的排序,就可以通过小数来插入了,如果两位不够用的时候,还可以扩充为3位等等 + +表设计中,不要用mysql的关键字作为字段和表名 + +## 规则14 pom中的注释要写清楚,为什么引用这个模块写到每个依赖上边 + +## 规则15 像小白一样思考,像专家一样行动 \ No newline at end of file diff --git a/kernel-a-rule/README.md b/kernel-a-rule/README.md new file mode 100644 index 000000000..9eee3e89b --- /dev/null +++ b/kernel-a-rule/README.md @@ -0,0 +1,18 @@ +整个框架每个模块需要遵守的规范 + +每个开发人员也需要遵守的规范 + +## 业务异常统一用ServiceException类 + +## 每个模块的异常枚举要在exception.enums包下维护 + +## 异常枚举分三类 + +异常分为三类,第一类用户操作异常的枚举以UserExceptionEnum结尾 + +第二类是系统业务逻辑异常,枚举以BusinessExceptionEnum结尾 + +第三类是第三方调用异常,枚举以ThirdExceptionEnum结尾 + +## 每个模块设立独立的模块异常,方便区分各个模块的异常 + diff --git a/kernel-a-rule/pom.xml b/kernel-a-rule/pom.xml new file mode 100644 index 000000000..1cd01655e --- /dev/null +++ b/kernel-a-rule/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-a-rule + + jar + + + + + + org.springframework.boot + spring-boot-starter + + + + + cn.hutool + hutool-all + + + + + org.projectlombok + lombok + + + + + com.alibaba + fastjson + + + + + + javax.servlet + javax.servlet-api + true + + + + + + org.springframework + spring-web + true + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/abstracts/AbstractExceptionEnum.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/abstracts/AbstractExceptionEnum.java new file mode 100644 index 000000000..5f31c4205 --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/abstracts/AbstractExceptionEnum.java @@ -0,0 +1,33 @@ +package cn.stylefeng.roses.kernel.rule.abstracts; + +/** + * 异常枚举格式规范,每个异常枚举都要实现这个接口 + *

+ * 为了在抛出ServiceException的时候规范抛出的内容 + *

+ * ServiceException抛出时必须为本接口的实现类 + * + * @author fengshuonan + * @date 2020/10/14 21:41 + */ +public interface AbstractExceptionEnum { + + /** + * 获取异常的状态码 + * + * @return 状态码 + * @author fengshuonan + * @date 2020/10/14 21:42 + */ + String getErrorCode(); + + /** + * 获取给用户提示信息 + * + * @return 提示信息 + * @author fengshuonan + * @date 2020/10/14 21:42 + */ + String getUserTip(); + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/abstracts/AbstractTreeNode.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/abstracts/AbstractTreeNode.java new file mode 100644 index 000000000..8cf15b381 --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/abstracts/AbstractTreeNode.java @@ -0,0 +1,40 @@ +package cn.stylefeng.roses.kernel.rule.abstracts; + +import java.util.List; + +/** + * 树形节点的抽象接口 + * + * @author fengshuonan + * @date 2020/10/15 14:31 + */ +public interface AbstractTreeNode { + + /** + * 获取节点id + * + * @return 节点的id标识 + * @author fengshuonan + * @date 2020/10/15 15:28 + */ + String getNodeId(); + + /** + * 获取节点父id + * + * @return 父节点的id + * @author fengshuonan + * @date 2020/10/15 15:28 + */ + String getNodeParentId(); + + /** + * 设置children + * + * @param childrenNodes 设置节点的子节点 + * @author fengshuonan + * @date 2020/10/15 15:28 + */ + void setChildrenNodes(List childrenNodes); + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/constants/RuleConstants.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/constants/RuleConstants.java new file mode 100644 index 000000000..54e193ead --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/constants/RuleConstants.java @@ -0,0 +1,52 @@ +package cn.stylefeng.roses.kernel.rule.constants; + +/** + * 规则模块的常量 + * + * @author fengshuonan + * @date 2020/10/16 11:25 + */ +public interface RuleConstants { + + /** + * 用户端操作异常的错误码分类编号 + */ + String USER_OPERATION_ERROR_TYPE_CODE = "A"; + + /** + * 业务执行异常的错误码分类编号 + */ + String BUSINESS_ERROR_TYPE_CODE = "B"; + + /** + * 第三方调用异常的错误码分类编号 + */ + String THIRD_ERROR_TYPE_CODE = "C"; + + /** + * 一级宏观码标识,宏观码标识代表一类错误码的统称 + */ + String FIRST_LEVEL_WIDE_CODE = "0001"; + + /** + * 请求成功的返回码 + */ + String SUCCESS_CODE = "00000"; + + /** + * 请求成功的返回信息 + */ + String SUCCESS_MESSAGE = "请求成功"; + + /** + * 规则模块的名称 + */ + String RULE_MODULE_NAME = "kernel-a-rule"; + + /** + * 异常枚举的步进值 + */ + String RULE_EXCEPTION_STEP_CODE = "01"; + + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/constants/TreeConstants.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/constants/TreeConstants.java new file mode 100644 index 000000000..00ad83b6b --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/constants/TreeConstants.java @@ -0,0 +1,21 @@ +package cn.stylefeng.roses.kernel.rule.constants; + +/** + * 构建树有关的常量 + * + * @author fengshuonan + * @date 2020/10/15 15:50 + */ +public interface TreeConstants { + + /** + * 根节点Id + */ + String ROOT_TREE_NODE_ID = "-1"; + + /** + * 根节点名称 + */ + String ROOT_TREE_NODE_NAME = "根结点"; + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/enums/SexEnum.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/enums/SexEnum.java new file mode 100644 index 000000000..dcd2865a2 --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/enums/SexEnum.java @@ -0,0 +1,49 @@ +package cn.stylefeng.roses.kernel.rule.enums; + +import lombok.Getter; + +/** + * 性别的枚举 + * + * @author fengshuonan + * @date 2020/10/17 10:01 + */ +@Getter +public enum SexEnum { + + /** + * 男 + */ + M("M", "男"), + + /** + * 女 + */ + F("F", "女"); + + private final String code; + + private final String message; + + SexEnum(String code, String message) { + this.code = code; + this.message = message; + } + + /** + * 根据code获取枚举 + * + * @author fengshuonan + * @date 2020/10/29 18:59 + */ + public static SexEnum codeToEnum(String code) { + if (null != code) { + for (SexEnum e : SexEnum.values()) { + if (e.getCode().equals(code)) { + return e; + } + } + } + return null; + } +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/enums/StatusEnum.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/enums/StatusEnum.java new file mode 100644 index 000000000..7f807f8b8 --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/enums/StatusEnum.java @@ -0,0 +1,74 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.rule.enums; + +import lombok.Getter; + +/** + * 公共状态,一般用来表示开启和关闭 + * + * @author fengshuonan + * @date 2020/10/14 21:31 + */ +@Getter +public enum StatusEnum { + + /** + * 启用 + */ + ENABLE(1, "启用"), + + /** + * 禁用 + */ + DISABLE(2, "禁用"); + + private final Integer code; + + private final String message; + + StatusEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + /** + * 根据code获取枚举 + * + * @author fengshuonan + * @date 2020/10/29 18:59 + */ + public static StatusEnum codeToEnum(Integer code) { + if (null != code) { + for (StatusEnum e : StatusEnum.values()) { + if (e.getCode().equals(code)) { + return e; + } + } + } + return null; + } + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/enums/YesOrNotEnum.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/enums/YesOrNotEnum.java new file mode 100644 index 000000000..8a37274ff --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/enums/YesOrNotEnum.java @@ -0,0 +1,33 @@ +package cn.stylefeng.roses.kernel.rule.enums; + +import lombok.Getter; + +/** + * 是或否的枚举,一般用在数据库字段,例如del_flag字段,char(1),填写Y或N + * + * @author fengshuonan + * @date 2020/4/13 22:59 + */ +@Getter +public enum YesOrNotEnum { + + /** + * 是 + */ + Y("Y", "是"), + + /** + * 否 + */ + N("N", "否"); + + private final String code; + + private final String message; + + YesOrNotEnum(String code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/base/ServiceException.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/base/ServiceException.java new file mode 100644 index 000000000..bfba75be5 --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/base/ServiceException.java @@ -0,0 +1,100 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.rule.exception.base; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import static cn.stylefeng.roses.kernel.rule.constants.RuleConstants.RULE_MODULE_NAME; + +/** + * 所有业务异常的基类 + *

+ * 在抛出异常时候,务必带上AbstractExceptionEnum枚举 + *

+ * 业务异常分为三种 + *

+ * 第一种是用户端操作的异常,例如用户输入参数为空,用户输入账号密码不正确 + *

+ * 第二种是当前系统业务逻辑出错,例如系统执行出错,磁盘空间不足 + *

+ * 第三种是第三方系统调用出错,例如文件服务调用失败,RPC调用超时 + * + * @author fengshuonan + * @date 2020/10/15 9:07 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ServiceException extends RuntimeException { + + /** + * 错误码 + */ + private String errorCode; + + /** + * 返回给用户的提示信息 + */ + private String userTip; + + /** + * 异常的模块名称 + */ + private String moduleName; + + /** + * 根据模块名,错误码,用户提示直接抛出异常 + */ + public ServiceException(String moduleName, String errorCode, String userTip) { + super(userTip); + this.errorCode = moduleName; + this.moduleName = errorCode; + this.userTip = userTip; + } + + /** + * 如果要直接抛出ServiceException,可以用这个构造函数 + */ + public ServiceException(String moduleName, AbstractExceptionEnum exception) { + super(exception.getUserTip()); + this.moduleName = moduleName; + this.errorCode = exception.getErrorCode(); + this.userTip = exception.getUserTip(); + } + + /** + * 不建议直接抛出ServiceException,因为这样无法确认是哪个模块抛出的异常 + *

+ * 建议使用业务异常时,都抛出自己模块的异常类 + */ + public ServiceException(AbstractExceptionEnum exception) { + super(exception.getUserTip()); + this.moduleName = RULE_MODULE_NAME; + this.errorCode = exception.getErrorCode(); + this.userTip = exception.getUserTip(); + } + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/defaults/DefaultBusinessExceptionEnum.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/defaults/DefaultBusinessExceptionEnum.java new file mode 100644 index 000000000..733391f3e --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/defaults/DefaultBusinessExceptionEnum.java @@ -0,0 +1,38 @@ +package cn.stylefeng.roses.kernel.rule.exception.enums.defaults; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import lombok.Getter; + +import static cn.stylefeng.roses.kernel.rule.constants.RuleConstants.BUSINESS_ERROR_TYPE_CODE; +import static cn.stylefeng.roses.kernel.rule.constants.RuleConstants.FIRST_LEVEL_WIDE_CODE; + +/** + * 系统执行出错,业务本身逻辑问题导致的错误(一级宏观码) + * + * @author fengshuonan + * @date 2020/10/15 17:18 + */ +@Getter +public enum DefaultBusinessExceptionEnum implements AbstractExceptionEnum { + + /** + * 系统执行出错(一级宏观错误码) + */ + SYSTEM_RUNTIME_ERROR(BUSINESS_ERROR_TYPE_CODE + FIRST_LEVEL_WIDE_CODE, "系统执行出错,请检查系统运行状况"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + DefaultBusinessExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/defaults/DefaultThirdExceptionEnum.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/defaults/DefaultThirdExceptionEnum.java new file mode 100644 index 000000000..702de69d3 --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/defaults/DefaultThirdExceptionEnum.java @@ -0,0 +1,38 @@ +package cn.stylefeng.roses.kernel.rule.exception.enums.defaults; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import lombok.Getter; + +import static cn.stylefeng.roses.kernel.rule.constants.RuleConstants.FIRST_LEVEL_WIDE_CODE; +import static cn.stylefeng.roses.kernel.rule.constants.RuleConstants.THIRD_ERROR_TYPE_CODE; + +/** + * 表示错误来源于第三方服务,比如 CDN 服务出错,消息投递超时等问题 + * + * @author fengshuonan + * @date 2020/10/15 17:31 + */ +@Getter +public enum DefaultThirdExceptionEnum implements AbstractExceptionEnum { + + /** + * 调用第三方服务出错(一级宏观错误码) + */ + THIRD_PARTY_ERROR(THIRD_ERROR_TYPE_CODE + FIRST_LEVEL_WIDE_CODE, "第三方调用出现错误"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + DefaultThirdExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/defaults/DefaultUserExceptionEnum.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/defaults/DefaultUserExceptionEnum.java new file mode 100644 index 000000000..40c276c83 --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/defaults/DefaultUserExceptionEnum.java @@ -0,0 +1,38 @@ +package cn.stylefeng.roses.kernel.rule.exception.enums.defaults; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import lombok.Getter; + +import static cn.stylefeng.roses.kernel.rule.constants.RuleConstants.FIRST_LEVEL_WIDE_CODE; +import static cn.stylefeng.roses.kernel.rule.constants.RuleConstants.USER_OPERATION_ERROR_TYPE_CODE; + +/** + * 源于用户操作的异常枚举,比如参数错误,用户安装版本过低,用户支付超时等问题 + * + * @author fengshuonan + * @date 2020/10/15 17:31 + */ +@Getter +public enum DefaultUserExceptionEnum implements AbstractExceptionEnum { + + /** + * 用户端错误(一级宏观错误码) + */ + USER_OPERATION_ERROR(USER_OPERATION_ERROR_TYPE_CODE + FIRST_LEVEL_WIDE_CODE, "执行失败,请检查操作是否正常"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + DefaultUserExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/http/ServletExceptionEnum.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/http/ServletExceptionEnum.java new file mode 100644 index 000000000..1d4e65dcc --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/exception/enums/http/ServletExceptionEnum.java @@ -0,0 +1,38 @@ +package cn.stylefeng.roses.kernel.rule.exception.enums.http; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import lombok.Getter; + +import static cn.stylefeng.roses.kernel.rule.constants.RuleConstants.BUSINESS_ERROR_TYPE_CODE; +import static cn.stylefeng.roses.kernel.rule.constants.RuleConstants.RULE_EXCEPTION_STEP_CODE; + +/** + * servlet相关业务异常 + * + * @author fengshuonan + * @date 2020/10/15 17:39 + */ +@Getter +public enum ServletExceptionEnum implements AbstractExceptionEnum { + + /** + * 获取不到http context异常 + */ + HTTP_CONTEXT_ERROR(BUSINESS_ERROR_TYPE_CODE + RULE_EXCEPTION_STEP_CODE + "01", "获取不到http context,请确认当前请求是http请求"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + ServletExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/factory/DefaultTreeBuildFactory.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/factory/DefaultTreeBuildFactory.java new file mode 100644 index 000000000..50c8bdfad --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/factory/DefaultTreeBuildFactory.java @@ -0,0 +1,94 @@ +package cn.stylefeng.roses.kernel.rule.factory; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractTreeNode; +import cn.stylefeng.roses.kernel.rule.factory.base.AbstractTreeBuildFactory; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 默认递归工具类,用于遍历有父子关系的节点,例如菜单树,字典树等等 + * + * @author fengshuonan + * @date 2018/7/25 下午5:59 + */ +@Data +public class DefaultTreeBuildFactory implements AbstractTreeBuildFactory { + + /** + * 顶级节点的父节点id(默认-1) + */ + private String rootParentId = "-1"; + + public DefaultTreeBuildFactory() { + + } + + public DefaultTreeBuildFactory(String rootParentId) { + this.rootParentId = rootParentId; + } + + @Override + public List doTreeBuild(List nodes) { + + // 将每个节点的构造一个子树 + for (T treeNode : nodes) { + this.buildChildNodes(nodes, treeNode, new ArrayList<>()); + } + + // 只保留上级是根节点的节点,也就是只留下所有一级节点 + ArrayList results = new ArrayList<>(); + for (T node : nodes) { + if (node.getNodeParentId().equals(rootParentId)) { + results.add(node); + } + } + + return results; + } + + /** + * 查询子节点的集合 + * + * @param totalNodes 所有节点的集合 + * @param node 被查询节点的id + * @param childNodeLists 被查询节点的子节点集合 + */ + private void buildChildNodes(List totalNodes, T node, List childNodeLists) { + if (totalNodes == null || node == null) { + return; + } + + List nodeSubLists = getSubChildsLevelOne(totalNodes, node); + + if (nodeSubLists.size() == 0) { + + } else { + for (T nodeSubList : nodeSubLists) { + buildChildNodes(totalNodes, nodeSubList, new ArrayList<>()); + } + } + + childNodeLists.addAll(nodeSubLists); + node.setChildrenNodes(childNodeLists); + } + + /** + * 获取子一级节点的集合 + * + * @param list 所有节点的集合 + * @param node 被查询节点的model + * @author fengshuonan + */ + private List getSubChildsLevelOne(List list, T node) { + List nodeList = new ArrayList<>(); + for (T nodeItem : list) { + if (nodeItem.getNodeParentId().equals(node.getNodeId())) { + nodeList.add(nodeItem); + } + } + return nodeList; + } + +} \ No newline at end of file diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/factory/TreeNodeFactory.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/factory/TreeNodeFactory.java new file mode 100644 index 000000000..fc0d3decc --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/factory/TreeNodeFactory.java @@ -0,0 +1,32 @@ +package cn.stylefeng.roses.kernel.rule.factory; + +import cn.stylefeng.roses.kernel.rule.pojo.tree.DefaultTreeNode; + +import static cn.stylefeng.roses.kernel.rule.constants.TreeConstants.ROOT_TREE_NODE_ID; +import static cn.stylefeng.roses.kernel.rule.constants.TreeConstants.ROOT_TREE_NODE_NAME; + +/** + * 创建树节点的工厂类 + * + * @author fengshuonan + * @date 2020/10/15 15:51 + */ +public class TreeNodeFactory { + + /** + * 创建一个根节点 + * + * @author fengshuonan + * @date 2020/10/15 15:52 + */ + public static DefaultTreeNode createRootNode() { + DefaultTreeNode root = new DefaultTreeNode(); + root.setChecked(false); + root.setId(ROOT_TREE_NODE_ID); + root.setName(ROOT_TREE_NODE_NAME); + root.setOpen(true); + root.setPId(null); + return root; + } + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/factory/base/AbstractTreeBuildFactory.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/factory/base/AbstractTreeBuildFactory.java new file mode 100644 index 000000000..85ef13f05 --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/factory/base/AbstractTreeBuildFactory.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.rule.factory.base; + +import java.util.List; + +/** + * 树构建的抽象类,定义构建tree的基本步骤 + * + * @author fengshuonan + * @date 2018/7/25 下午5:59 + */ +public interface AbstractTreeBuildFactory { + + /** + * 树节点构建整体过程 + * + * @param nodes 被处理的节点集合 + * @return 被处理后的节点集合(带树形结构了) + * @author fengshuonan + * @date 2018/7/26 上午9:45 + */ + List doTreeBuild(List nodes); + +} \ No newline at end of file diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/dict/SimpleDict.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/dict/SimpleDict.java new file mode 100644 index 000000000..dbf9952a4 --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/dict/SimpleDict.java @@ -0,0 +1,35 @@ +package cn.stylefeng.roses.kernel.rule.pojo.dict; + +import lombok.Data; + +/** + * 只包含id,name,code三个字段的pojo + *

+ * 一般用在获取某个业务的下拉列表的返回bean + *

+ * 例如,返回用户下拉列表,只需返回用户id和姓名即可 + *

+ * 例如,返回角色下拉列表,只需返回角色id和角色名称 + * + * @author fengshuonan + * @date 2020/11/21 16:53 + */ +@Data +public class SimpleDict { + + /** + * id + */ + private Long id; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/request/BaseRequest.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/request/BaseRequest.java new file mode 100644 index 000000000..7dbe07870 --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/request/BaseRequest.java @@ -0,0 +1,132 @@ +package cn.stylefeng.roses.kernel.rule.pojo.request; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Map; + +/** + * 请求基类,所有接口请求可继承此类 + * + * @author fengshuonan + * @date 2020/10/14 18:12 + */ +@Data +public class BaseRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 开始时间 + */ + private String searchBeginTime; + + /** + * 结束时间 + */ + private String searchEndTime; + + /** + * 分页:每页大小(默认20) + */ + private Integer pageSize; + + /** + * 分页:第几页(从1开始) + */ + private Integer pageNo; + + /** + * 排序字段 + */ + private String orderBy; + + /** + * 正序或者倒序排列(asc 或 desc) + */ + private String sortBy; + + /** + * 其他参数(如有需要) + */ + private Map otherParams; + + /** + * 参数校验分组:分页 + */ + public @interface page { + } + + /** + * 参数校验分组:查询所有 + */ + public @interface list { + } + + /** + * 参数校验分组:增加 + */ + public @interface add { + } + + /** + * 参数校验分组:编辑 + */ + public @interface edit { + } + + /** + * 参数校验分组:删除 + */ + public @interface delete { + } + + /** + * 参数校验分组:详情 + */ + public @interface detail { + } + + /** + * 参数校验分组:导出 + */ + public @interface export { + } + + /** + * 参数校验分组:修改状态 + */ + public @interface updateStatus { + } + + /** + * 预留组1,用来给特殊业务的参数校验用 + */ + public @interface groupOne { + } + + /** + * 预留组2,用来给特殊业务的参数校验用 + */ + public @interface groupTwo { + } + + /** + * 预留组3,用来给特殊业务的参数校验用 + */ + public @interface groupThree { + } + + /** + * 预留组4,用来给特殊业务的参数校验用 + */ + public @interface groupFour { + } + + /** + * 预留组5,用来给特殊业务的参数校验用 + */ + public @interface groupFive { + } + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/response/ErrorResponseData.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/response/ErrorResponseData.java new file mode 100644 index 000000000..41dff6e3a --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/response/ErrorResponseData.java @@ -0,0 +1,28 @@ +package cn.stylefeng.roses.kernel.rule.pojo.response; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 请求失败的结果包装类 + * + * @author fengshuonan + * @date 2020/10/16 16:26 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ErrorResponseData extends ResponseData { + + /** + * 异常的具体类名称 + */ + private String exceptionClazz; + + public ErrorResponseData(String code, String message) { + super(Boolean.FALSE, code, message, null); + } + + ErrorResponseData(String code, String message, Object object) { + super(Boolean.FALSE, code, message, object); + } +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/response/ResponseData.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/response/ResponseData.java new file mode 100644 index 000000000..9507eb47e --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/response/ResponseData.java @@ -0,0 +1,44 @@ +package cn.stylefeng.roses.kernel.rule.pojo.response; + +import lombok.Data; + +/** + * http响应结果封装 + * + * @author fengshuonan + * @date 2020/10/17 17:33 + */ +@Data +public class ResponseData { + + /** + * 请求是否成功 + */ + private Boolean success; + + /** + * 响应状态码 + */ + private String code; + + /** + * 响应信息 + */ + private String message; + + /** + * 响应对象 + */ + private Object data; + + public ResponseData() { + } + + public ResponseData(Boolean success, String code, String message, Object data) { + this.success = success; + this.code = code; + this.message = message; + this.data = data; + } + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/response/SuccessResponseData.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/response/SuccessResponseData.java new file mode 100644 index 000000000..3e03d0bd1 --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/response/SuccessResponseData.java @@ -0,0 +1,24 @@ +package cn.stylefeng.roses.kernel.rule.pojo.response; + +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; + +/** + * 响应成功的封装类 + * + * @author fengshuonan + * @date 2020/10/16 16:23 + */ +public class SuccessResponseData extends ResponseData { + + public SuccessResponseData() { + super(Boolean.TRUE, RuleConstants.SUCCESS_CODE, RuleConstants.SUCCESS_MESSAGE, null); + } + + public SuccessResponseData(Object object) { + super(Boolean.TRUE, RuleConstants.SUCCESS_CODE, RuleConstants.SUCCESS_MESSAGE, object); + } + + public SuccessResponseData(String code, String message, Object object) { + super(Boolean.TRUE, code, message, object); + } +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/tree/DefaultTreeNode.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/tree/DefaultTreeNode.java new file mode 100644 index 000000000..3be374e71 --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/pojo/tree/DefaultTreeNode.java @@ -0,0 +1,70 @@ +package cn.stylefeng.roses.kernel.rule.pojo.tree; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractTreeNode; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 默认树节点的封装 + *

+ * 默认的根节点id是-1,名称是根节点 + * + * @author fengshuonan + * @date 2020/10/15 14:39 + */ +@Data +public class DefaultTreeNode implements AbstractTreeNode { + + /** + * 节点id + */ + private String id; + + /** + * 父节点id + */ + private String pId; + + /** + * 节点名称 + */ + private String name; + + /** + * 是否打开节点 + */ + private Boolean open; + + /** + * 是否被选中 + */ + private Boolean checked = false; + + /** + * 排序,越小越靠前 + */ + private BigDecimal sort; + + /** + * 子节点 + */ + private List children; + + @Override + public String getNodeId() { + return id; + } + + @Override + public String getNodeParentId() { + return pId; + } + + @Override + public void setChildrenNodes(List childrenNodes) { + this.children = childrenNodes; + } + +} diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/util/AopTargetUtils.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/util/AopTargetUtils.java new file mode 100644 index 000000000..f3b979499 --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/util/AopTargetUtils.java @@ -0,0 +1,75 @@ +package cn.stylefeng.roses.kernel.rule.util; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.framework.AdvisedSupport; +import org.springframework.aop.framework.AopProxy; +import org.springframework.aop.support.AopUtils; + +import java.lang.reflect.Field; + +/** + * 获取代理原始对象的工具 + * + * @author fengshuonan + * @date 2020/10/19 16:20 + */ +@Slf4j +public class AopTargetUtils { + + /** + * 获取代理对象的原始对象 + * + * @author fengshuonan + * @date 2020/10/19 16:21 + */ + public static Object getTarget(Object proxy) { + + // 不是代理对象,直接返回参数对象 + if (!AopUtils.isAopProxy(proxy)) { + return proxy; + } + + // 判断是否是jdk还是cglib代理的对象 + try { + if (AopUtils.isJdkDynamicProxy(proxy)) { + return getJdkDynamicProxyTargetObject(proxy); + } else { + return getCglibProxyTargetObject(proxy); + } + } catch (Exception e) { + log.error("获取代理对象异常", e); + return null; + } + } + + /** + * 获取cglib代理的对象 + * + * @author fengshuonan + * @date 2020/10/19 16:21 + */ + private static Object getCglibProxyTargetObject(Object proxy) throws Exception { + Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0"); + h.setAccessible(true); + Object dynamicAdvisedInterceptor = h.get(proxy); + Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised"); + advised.setAccessible(true); + return ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget(); + } + + /** + * 获取jdk代理的对象 + * + * @author fengshuonan + * @date 2020/10/19 16:22 + */ + private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception { + Field h = proxy.getClass().getSuperclass().getDeclaredField("h"); + h.setAccessible(true); + AopProxy aopProxy = (AopProxy) h.get(proxy); + Field advised = aopProxy.getClass().getDeclaredField("advised"); + advised.setAccessible(true); + return ((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget(); + } + +} \ No newline at end of file diff --git a/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/util/HttpServletUtil.java b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/util/HttpServletUtil.java new file mode 100644 index 000000000..5836b00aa --- /dev/null +++ b/kernel-a-rule/src/main/java/cn/stylefeng/roses/kernel/rule/util/HttpServletUtil.java @@ -0,0 +1,162 @@ +package cn.stylefeng.roses.kernel.rule.util; + +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import cn.stylefeng.roses.kernel.rule.exception.enums.http.ServletExceptionEnum; +import com.alibaba.fastjson.JSONPath; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 保存Http请求的上下文,在任何地方快速获取HttpServletRequest和HttpServletResponse + * + * @author fengshuonan + * @date 2020/10/15 17:38 + */ +@Slf4j +public class HttpServletUtil { + + /** + * 本机ip地址 + */ + private static final String LOCAL_IP = "127.0.0.1"; + + /** + * 本机ip地址的ipv6地址 + */ + private static final String LOCAL_REMOTE_HOST = "0:0:0:0:0:0:0:1"; + + /** + * 获取用户浏览器信息的http请求header + */ + private static final String USER_AGENT_HTTP_HEADER = "User-Agent"; + + /** + * 获取当前请求的request对象 + * + * @author fengshuonan + * @date 2020/10/15 17:48 + */ + public static HttpServletRequest getRequest() { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (requestAttributes == null) { + throw new ServiceException(ServletExceptionEnum.HTTP_CONTEXT_ERROR); + } else { + return requestAttributes.getRequest(); + } + } + + /** + * 获取当前请求的response对象 + * + * @author fengshuonan + * @date 2020/10/15 17:48 + */ + public static HttpServletResponse getResponse() { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (requestAttributes == null) { + throw new ServiceException(ServletExceptionEnum.HTTP_CONTEXT_ERROR); + } else { + return requestAttributes.getResponse(); + } + } + + /** + * 获取客户端ip + *

+ * 如果获取不到或者获取到的是ipv6地址,都返回127.0.0.1 + * + * @author fengshuonan + * @date 2020/10/26 14:09 + */ + public static String getRequestClientIp(HttpServletRequest request) { + if (ObjectUtil.isEmpty(request)) { + return LOCAL_IP; + } else { + String remoteHost = ServletUtil.getClientIP(request); + return LOCAL_REMOTE_HOST.equals(remoteHost) ? LOCAL_IP : remoteHost; + } + } + + /** + * 根据http请求的client ip定位城市等信息 + * + * @param request http请求封装 + * @param ipGeoApi 阿里云ip定位api接口 + * @param ipGeoAppCode 阿里云ip定位appCode + * @author fengshuonan + * @date 2020/10/26 14:10 + */ + @SuppressWarnings("unchecked") + public static String calcClientIpAddress(HttpServletRequest request, String ipGeoApi, String ipGeoAppCode) { + + // 如果获取不到,返回 "-" + String resultJson = "-"; + + // 请求阿里云定位接口需要传的header的名称 + String requestApiHeader = "Authorization"; + + // 获取客户端的ip地址 + String ip = getRequestClientIp(request); + + // 如果是本地ip或局域网ip,则直接不查询 + if (ObjectUtil.isEmpty(ip) || NetUtil.isInnerIP(ip)) { + return resultJson; + } + + // 判断定位api和appCode是否为空 + if (ObjectUtil.hasEmpty(ipGeoApi, ipGeoAppCode)) { + return resultJson; + } + + try { + if (ObjectUtil.isAllNotEmpty(ipGeoApi, ipGeoAppCode)) { + String jsonPath = "$['data']['country','region','city','isp']"; + String appCodeSymbol = "APPCODE"; + HttpRequest http = HttpUtil.createGet(String.format(ipGeoApi, ip)); + http.header(requestApiHeader, appCodeSymbol + " " + ipGeoAppCode); + resultJson = http.timeout(3000).execute().body(); + resultJson = String.join("", (List) JSONPath.read(resultJson, jsonPath)); + } + } catch (Exception e) { + log.error(">>> 根据ip定位异常,具体信息为:{}", e.getMessage()); + } + return resultJson; + } + + /** + * 根据http请求获取UserAgent信息 + *

+ * UserAgent信息包含浏览器的版本,客户端操作系统等信息 + *

+ * 没有相关header被解析,则返回null + * + * @author fengshuonan + * @date 2020/10/28 9:14 + */ + public static UserAgent getUserAgent(HttpServletRequest request) { + + // 获取http header的内容 + String userAgentStr = ServletUtil.getHeaderIgnoreCase(request, USER_AGENT_HTTP_HEADER); + + // 如果http header内容不为空,则解析这个字符串获取UserAgent对象 + if (ObjectUtil.isNotEmpty(userAgentStr)) { + return UserAgentUtil.parse(userAgentStr); + } else { + return null; + } + + } + +} diff --git a/kernel-d-auth/README.md b/kernel-d-auth/README.md new file mode 100644 index 000000000..08d85d925 --- /dev/null +++ b/kernel-d-auth/README.md @@ -0,0 +1,11 @@ +# 权限模块 + +## 系统需要认证的情况下使用 + +## auth包含认证和鉴权 + +认证和鉴权的区别: + +认证校验你能否登录系统,认证的过程是校验token的过程 + +鉴权校验你有系统的哪些权限,鉴权的过程是校验角色是否包含某些接口的权限 \ No newline at end of file diff --git a/kernel-d-auth/auth-api/README.md b/kernel-d-auth/auth-api/README.md new file mode 100644 index 000000000..e2e13aa67 --- /dev/null +++ b/kernel-d-auth/auth-api/README.md @@ -0,0 +1 @@ +权限模块api \ No newline at end of file diff --git a/kernel-d-auth/auth-api/pom.xml b/kernel-d-auth/auth-api/pom.xml new file mode 100644 index 000000000..25985836b --- /dev/null +++ b/kernel-d-auth/auth-api/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-auth + 1.0.0 + ../pom.xml + + + auth-api + + jar + + + + + + + cn.stylefeng.roses + config-api + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/AuthServiceApi.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/AuthServiceApi.java new file mode 100644 index 000000000..5af2a4788 --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/AuthServiceApi.java @@ -0,0 +1,81 @@ +package cn.stylefeng.roses.kernel.auth.api; + +import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; +import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginRequest; +import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginResponse; + +/** + * 认证服务的接口,包括基本的登录退出操作和校验token等操作 + * + * @author fengshuonan + * @date 2020/10/26 14:41 + */ +public interface AuthServiceApi { + + /** + * 常规登录操作 + * + * @param loginRequest 登录的请求 + * @return token 一般为jwt token + * @author fengshuonan + * @date 2020/10/26 14:41 + */ + LoginResponse login(LoginRequest loginRequest); + + /** + * 登录(直接用账号登录),一般用在第三方登录 + * + * @param username 账号 + * @author fengshuonan + * @date 2020/10/26 14:40 + */ + LoginResponse loginWithUserName(String username); + + /** + * 当前登录人退出登录 + * + * @author fengshuonan + * @date 2020/10/19 14:16 + */ + void logout(); + + /** + * 移除某个token,也就是退出某个用户 + * + * @param token 某个用户的登录token + * @author fengshuonan + * @date 2020/10/19 14:16 + */ + void logoutWithToken(String token); + + /** + * 校验token + * + * @param token 某个用户的登录token + * @throws AuthException 认证异常,如果token错误或过期,会有相关的异常抛出 + * @author fengshuonan + * @date 2020/10/19 14:16 + */ + void validateToken(String token) throws AuthException; + + /** + * 获取token是否正确 + * + * @param token 某个用户的登录token + * @return true-token正确,false-token错误 + * @author fengshuonan + * @date 2020/10/19 14:16 + */ + boolean getTokenFlag(String token); + + /** + * 校验用户访问的url是否认证通过 + * + * @param token 用户登陆的token + * @param requestUrl 被校验的url + * @author fengshuonan + * @date 2020/10/22 16:03 + */ + void checkAuth(String token, String requestUrl); + +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/LoginUserApi.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/LoginUserApi.java new file mode 100644 index 000000000..4003129e1 --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/LoginUserApi.java @@ -0,0 +1,86 @@ +package cn.stylefeng.roses.kernel.auth.api; + +import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; + +/** + * 当前登陆用户相关的一系列方法 + * + * @author fengshuonan + * @date 2020/10/17 10:27 + */ +public interface LoginUserApi { + + /** + * 获取当前登陆用户的token + *

+ * 如果获取不到,返回null + * + * @return 当前用户的token或null + * @author fengshuonan + * @date 2020/10/17 11:05 + */ + String getToken(); + + /** + * 获取当前登陆用户 + *

+ * 如果获取不到当前登陆用户会抛出 AuthException + * + * @return 当前登陆用户信息 + * @throws AuthException 权限异常 + * @author fengshuonan + * @date 2020/10/17 10:27 + */ + LoginUser getLoginUser() throws AuthException; + + /** + * 获取当前登陆用户 + *

+ * 如果获取不到当前登陆用户返回null + * + * @return 当前登录用户信息 + * @author fengshuonan + * @date 2020/10/17 11:00 + */ + LoginUser getLoginUserNullable(); + + /** + * 获取是否是超级管理员的标识 + * + * @return true-是超级管理员,false-不是超级管理员 + * @author fengshuonan + * @date 2020/11/4 15:45 + */ + boolean getSuperAdminFlag(); + + /** + * 判断当前用户是否登录 + * + * @return 是否登录,true是,false否 + * @author fengshuonan + * @date 2020/10/17 11:02 + */ + boolean hasLogin(); + + /** + * 判断当前登录用户是否有某资源的访问权限 + * + * @param requestUri 请求的url,例如: /userInfo/list + * @return 是否有访问权限,true是,false否 + * @author fengshuonan + * @date 2020/10/17 11:03 + */ + boolean hasPermission(String requestUri); + + /** + * 判断当前登录用户是否包含某个角色 + * + * @param roleCode 角色编码 + * @return 是否包含该角色,true是,false否 + * @author fengshuonan + * @date 2020/10/17 11:04 + */ + boolean hasRole(String roleCode); + +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/PermissionServiceApi.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/PermissionServiceApi.java new file mode 100644 index 000000000..94837be86 --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/PermissionServiceApi.java @@ -0,0 +1,26 @@ +package cn.stylefeng.roses.kernel.auth.api; + +import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; + +/** + * 权限相关的服务接口 + * + * @author fengshuonan + * @date 2020/10/19 14:24 + */ +public interface PermissionServiceApi { + + /** + * 校验当前用户是否有某个接口的权限 + *

+ * 只要认证不通过,则会抛出异常 + * + * @param token 用户登陆的token + * @param requestUrl 被校验的url + * @throws AuthException 认证失败的异常信息 + * @author fengshuonan + * @date 2020/10/19 14:50 + */ + void checkPermission(String token, String requestUrl) throws AuthException; + +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/SessionManagerApi.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/SessionManagerApi.java new file mode 100644 index 000000000..ff8a701fd --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/SessionManagerApi.java @@ -0,0 +1,76 @@ +package cn.stylefeng.roses.kernel.auth.api; + +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; + +/** + * 用户会话管理 + *

+ * 会话指的是用户登录后和服务器一直保持一个交互状态的维护 + *

+ * 会话具有时效性,反之,当用户不再访问系统的时候,会话应该自动失效 + * + * @author fengshuonan + * @date 2020/10/19 16:47 + */ +public interface SessionManagerApi { + + /** + * 创建会话 + * + * @param token 用户登录的token + * @param loginUser 登录的用户 + * @author fengshuonan + * @date 2020/10/19 16:47 + */ + void createSession(String token, LoginUser loginUser); + + /** + * 通过token获取会话 + * + * @param token 用户token + * @return token对应用户的详细信息 + * @author fengshuonan + * @date 2020/10/19 16:48 + */ + LoginUser getSession(String token); + + /** + * 根据token删除一个会话 + * + * @param token 用户token + * @author fengshuonan + * @date 2020/10/19 16:48 + */ + void removeSession(String token); + + /** + * 删除用户所有的会话,但除了参数传的token的会话 + *

+ * 用在单端登录中,一个账号只能在一个浏览器登录 + * + * @param token 用户token + * @author fengshuonan + * @date 2020/10/21 16:18 + */ + void removeSessionExcludeToken(String token); + + /** + * 判断一个token是否还存在会话状态 + * + * @param token 用户token + * @return true-存在会话,false-不存在会话或者失效了 + * @author fengshuonan + * @date 2020/10/19 16:49 + */ + boolean haveSession(String token); + + /** + * 刷新会话的过期时间,刷新后用户当前的过期时间将重置 + * + * @param token 用户的token + * @author fengshuonan + * @date 2020/10/19 16:50 + */ + void refreshSession(String token); + +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/constants/AuthConstants.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/constants/AuthConstants.java new file mode 100644 index 000000000..810485e00 --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/constants/AuthConstants.java @@ -0,0 +1,46 @@ +package cn.stylefeng.roses.kernel.auth.api.constants; + +/** + * auth,鉴权模块的常量 + * + * @author fengshuonan + * @date 2020/10/16 11:05 + */ +public interface AuthConstants { + + /** + * auth模块的名称 + */ + String AUTH_MODULE_NAME = "kernel-d-auth"; + + /** + * 异常枚举的步进值 + */ + String AUTH_EXCEPTION_STEP_CODE = "03"; + + /** + * 登录用户的缓存前缀 + */ + String LOGGED_TOKEN_PREFIX = "LOGGED_TOKEN_"; + + /** + * 登录用户id的缓存前缀 + */ + String LOGGED_USERID_PREFIX = "LOGGED_USERID_"; + + /** + * 默认http请求携带token的header名称 + */ + String DEFAULT_AUTH_HEADER_NAME = "Authorization"; + + /** + * 获取http请求携带token的param的名称 + */ + String DEFAULT_AUTH_PARAM_NAME = "token"; + + /** + * 默认密码 + */ + String DEFAULT_PASSWORD = "123456"; + +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/context/LoginContext.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/context/LoginContext.java new file mode 100644 index 000000000..ea29e5a6d --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/context/LoginContext.java @@ -0,0 +1,18 @@ +package cn.stylefeng.roses.kernel.auth.api.context; + +import cn.hutool.extra.spring.SpringUtil; +import cn.stylefeng.roses.kernel.auth.api.LoginUserApi; + +/** + * 快速获取当前登陆用户的一系列操作方法,具体实现在Spring容器中查找 + * + * @author fengshuonan + * @date 2020/10/17 10:30 + */ +public class LoginContext { + + public static LoginUserApi me() { + return SpringUtil.getBean(LoginUserApi.class); + } + +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/enums/DataScopeTypeEnum.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/enums/DataScopeTypeEnum.java new file mode 100644 index 000000000..07139f755 --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/enums/DataScopeTypeEnum.java @@ -0,0 +1,70 @@ +package cn.stylefeng.roses.kernel.auth.api.enums; + +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; +import lombok.Getter; + +import static cn.stylefeng.roses.kernel.auth.api.exception.enums.AuthExceptionEnum.DATA_SCOPE_ERROR; + +/** + * 数据范围类型枚举,数据范围的值越小,数据权限越小 + * + * @author fengshuonan + * @date 2020/11/5 15:22 + */ +@Getter +public enum DataScopeTypeEnum { + + /** + * 仅本人数据 + */ + SELF(10, "仅本人数据"), + + /** + * 本部门数据 + */ + DEPT(20, "本部门数据"), + + /** + * 本部门及以下数据 + */ + DEPT_WITH_CHILD(30, "本部门及以下数据"), + + /** + * 指定部门数据 + */ + DEFINE(40, "指定部门数据"), + + /** + * 全部数据 + */ + ALL(50, "全部数据"); + + private final Integer code; + + private final String message; + + DataScopeTypeEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + /** + * 根据code获取枚举 + * + * @author fengshuonan + * @date 2020/10/29 18:59 + */ + public static DataScopeTypeEnum codeToEnum(Integer code) { + if (null != code) { + for (DataScopeTypeEnum dataScopeTypeEnum : DataScopeTypeEnum.values()) { + if (dataScopeTypeEnum.getCode().equals(code)) { + return dataScopeTypeEnum; + } + } + } + String userTip = StrUtil.format(DATA_SCOPE_ERROR.getUserTip(), code); + throw new AuthException(DATA_SCOPE_ERROR, userTip); + } + +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/AuthException.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/AuthException.java new file mode 100644 index 000000000..170e5b19a --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/AuthException.java @@ -0,0 +1,27 @@ +package cn.stylefeng.roses.kernel.auth.api.exception; + +import cn.stylefeng.roses.kernel.auth.api.constants.AuthConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; + +/** + * 认证类异常 + * + * @author fengshuonan + * @date 2020/10/15 15:59 + */ +public class AuthException extends ServiceException { + + public AuthException(String errorCode, String userTip) { + super(AuthConstants.AUTH_MODULE_NAME, errorCode, userTip); + } + + public AuthException(AbstractExceptionEnum exception, String userTip) { + super(AuthConstants.AUTH_MODULE_NAME, exception.getErrorCode(), userTip); + } + + public AuthException(AbstractExceptionEnum exception) { + super(AuthConstants.AUTH_MODULE_NAME, exception); + } + +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/enums/AuthExceptionEnum.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/enums/AuthExceptionEnum.java new file mode 100644 index 000000000..0010ab052 --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/exception/enums/AuthExceptionEnum.java @@ -0,0 +1,87 @@ +package cn.stylefeng.roses.kernel.auth.api.exception.enums; + +import cn.stylefeng.roses.kernel.auth.api.constants.AuthConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import lombok.Getter; + +/** + * 认证相关异常 + * + * @author fengshuonan + * @date 2020/10/16 10:53 + */ +@Getter +public enum AuthExceptionEnum implements AbstractExceptionEnum { + + /** + * 认证异常 + */ + AUTH_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "01", "认证失败,请检查您的操作是否正确"), + + /** + * 登陆时,账号或密码为空 + */ + PARAM_EMPTY(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "02", "登陆失败,账号或密码参数为空"), + + /** + * 账号或密码错误 + */ + USERNAME_PASSWORD_ERROR(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "03", "账号或密码错误"), + + /** + * 用户状态异常,可能被禁用可能被冻结,用StrUtil.format()格式化 + */ + USER_STATUS_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "04", "当前用户被{},请检查用户状态是否正常"), + + /** + * 登陆失败,账号参数为空 + */ + ACCOUNT_IS_BLANK(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "05", "登陆失败,账号参数为空"), + + /** + * 获取token失败 + */ + TOKEN_GET_ERROR(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "06", "获取token失败,请检查header和param中是否传递了用户token"), + + /** + * 获取资源为空 + */ + RESOURCE_DEFINITION_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "07", "获取资源为空,请检查当前请求url是否存在对应的ResourceDefinition"), + + /** + * 获取不到token对应的用户信息,请检查登录是否过期 + */ + TOKEN_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "08", "获取不到token对应的用户信息,请检查登录是否过期"), + + /** + * 权限校验失败,请检查用户是否有该资源的权限 + */ + PERMISSION_RES_VALIDATE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "09", "权限校验失败,请检查用户是否有该资源的权限"), + + /** + * 数据范围类型转化异常 + */ + DATA_SCOPE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "10", "数据范围类型转化异常,数据范围类型为:{}"), + + /** + * 权限校验失败,只有超级管理员可以授权所有数据 + */ + ONLY_SUPER_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + AuthConstants.AUTH_EXCEPTION_STEP_CODE + "11", "权限校验失败,只有超级管理员可以授权所有数据"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + AuthExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/expander/AuthConfigExpander.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/expander/AuthConfigExpander.java new file mode 100644 index 000000000..a2fb9fd7d --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/expander/AuthConfigExpander.java @@ -0,0 +1,91 @@ +package cn.stylefeng.roses.kernel.auth.api.expander; + +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.config.api.context.ConfigContext; + +import java.util.ArrayList; +import java.util.List; + +import static cn.stylefeng.roses.kernel.auth.api.constants.AuthConstants.*; + +/** + * 权限相关配置快速获取 + * + * @author fengshuonan + * @date 2020/10/17 16:10 + */ +public class AuthConfigExpander { + + /** + * 获取不被权限控制的url + * + * @author fengshuonan + * @date 2020/10/17 16:12 + */ + public static List getNoneSecurityConfig() { + String noneSecurityUrls = ConfigContext.me().getSysConfigValueWithDefault("SYS_NONE_SECURITY_URLS", String.class, ""); + if (StrUtil.isEmpty(noneSecurityUrls)) { + return new ArrayList<>(); + } else { + return StrUtil.split(noneSecurityUrls, ','); + } + } + + /** + * 获取session过期时间,默认3600秒 + *

+ * 在这个时段内不操作,会将用户踢下线,从新登陆 + *

+ * 关于记住我功能,如果开启了记住我功能,这个参数 + * + * @author fengshuonan + * @date 2020/10/20 9:32 + */ + public static Long getSessionExpiredSeconds() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_SESSION_EXPIRED_SECONDS", Long.class, 3600L); + } + + /** + * 获取单账号单端登录的开关 + *

+ * 单账号单端登录为限制一个账号多个浏览器登录 + * + * @return true-开启单端限制,false-关闭单端限制 + * @author fengshuonan + * @date 2020/10/21 14:31 + */ + public static boolean getSingleAccountLoginFlag() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_SINGLE_ACCOUNT_LOGIN_FLAG", Boolean.class, false); + } + + /** + * 获取携带token的header头的名称 + * + * @author fengshuonan + * @date 2020/10/22 14:11 + */ + public static String getAuthTokenHeaderName() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_AUTH_HEADER_NAME", String.class, DEFAULT_AUTH_HEADER_NAME); + } + + /** + * 获取携带token的header头的名称 + * + * @author fengshuonan + * @date 2020/10/22 14:11 + */ + public static String getAuthTokenParamName() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_AUTH_PARAM_NAME", String.class, DEFAULT_AUTH_PARAM_NAME); + } + + /** + * 获取默认密码 + * + * @author luojie + * @date 2020/11/6 10:05 + */ + public static String getDefaultPassWord() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_DEFAULT_PASSWORD", String.class, DEFAULT_PASSWORD); + } + +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginRequest.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginRequest.java new file mode 100644 index 000000000..1547a21a5 --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginRequest.java @@ -0,0 +1,32 @@ +package cn.stylefeng.roses.kernel.auth.api.pojo.auth; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 登录的请求参数 + * + * @author fengshuonan + * @date 2020/10/19 14:02 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class LoginRequest extends BaseRequest { + + /** + * 账号 + */ + private String account; + + /** + * 密码 + */ + private String password; + + /** + * 记住我 + */ + private Boolean rememberMe; + +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginResponse.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginResponse.java new file mode 100644 index 000000000..fea8e5b3e --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/auth/LoginResponse.java @@ -0,0 +1,30 @@ +package cn.stylefeng.roses.kernel.auth.api.pojo.auth; + +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; +import lombok.Data; + +/** + * 登录操作的响应结果 + * + * @author fengshuonan + * @date 2020/10/19 14:17 + */ +@Data +public class LoginResponse { + + /** + * 登录人的信息 + */ + private LoginUser loginUser; + + /** + * 登录人的token + */ + private String token; + + public LoginResponse(LoginUser loginUser, String token) { + this.loginUser = loginUser; + this.token = token; + } + +} diff --git a/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/login/LoginUser.java b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/login/LoginUser.java new file mode 100644 index 000000000..2c0bfe854 --- /dev/null +++ b/kernel-d-auth/auth-api/src/main/java/cn/stylefeng/roses/kernel/auth/api/pojo/login/LoginUser.java @@ -0,0 +1,117 @@ +package cn.stylefeng.roses.kernel.auth.api.pojo.login; + +import cn.hutool.core.lang.Dict; +import cn.stylefeng.roses.kernel.auth.api.enums.DataScopeTypeEnum; +import cn.stylefeng.roses.kernel.rule.pojo.dict.SimpleDict; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.Set; + +/** + * 登录用户信息 + * + * @author fengshuonan + * @date 2020/10/17 9:58 + */ +@Data +public class LoginUser implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 用户主键id + */ + private Long id; + + /** + * 账号 + */ + private String account; + + /** + * 姓名 + */ + private String name; + + /** + * 公司/组织id + */ + private Long organizationId; + + /** + * 头像(图片最终访问的url) + */ + private String avatar; + + /** + * 生日 + */ + private Date birthday; + + /** + * 性别(具体SexEnum枚举类)(M-男,F-女) + */ + private String sex; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机 + */ + private String mobilePhone; + + /** + * 固定电话 + */ + private String tel; + + /** + * 超级管理员标识,true-是超级管理员 + */ + private Boolean superAdmin; + + /** + * 用户数据范围类型的集合 + */ + private Set dataScopeTypes; + + /** + * 用户数据范围(userId的集合) + *

+ * 用户能看哪些用户数据的权限 + */ + private Set userIdDataScope; + + /** + * 组织机构数据范围(组织架构id的集合) + *

+ * 用户能看哪些组织机构数据的信息 + */ + private Set organizationIdDataScope; + + /** + * 具备应用信息 + */ + private Set apps; + + /** + * 角色信息 + */ + private Set roles; + + /** + * 可用资源集合 + */ + private Set resourceUrls; + + /** + * 其他信息,Dict为Map的拓展 + */ + private Dict otherInfos; + +} diff --git a/kernel-d-auth/auth-sdk/README.md b/kernel-d-auth/auth-sdk/README.md new file mode 100644 index 000000000..ea099002a --- /dev/null +++ b/kernel-d-auth/auth-sdk/README.md @@ -0,0 +1 @@ +认证和鉴权的sdk \ No newline at end of file diff --git a/kernel-d-auth/auth-sdk/pom.xml b/kernel-d-auth/auth-sdk/pom.xml new file mode 100644 index 000000000..8d00a7a83 --- /dev/null +++ b/kernel-d-auth/auth-sdk/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-auth + 1.0.0 + ../pom.xml + + + auth-sdk + + jar + + + + + + cn.stylefeng.roses + auth-api + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + system-api + 1.0.0 + + + + + + cn.stylefeng.roses + jwt-api + 1.0.0 + + + + + + javax.servlet + javax.servlet-api + + + org.springframework + spring-web + + + + + + org.springframework.boot + spring-boot-starter-data-redis + true + + + + + diff --git a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java new file mode 100644 index 000000000..5bfc888c3 --- /dev/null +++ b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/AuthServiceImpl.java @@ -0,0 +1,186 @@ +package cn.stylefeng.roses.kernel.auth.auth; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.BCrypt; +import cn.stylefeng.roses.kernel.auth.api.AuthServiceApi; +import cn.stylefeng.roses.kernel.auth.api.SessionManagerApi; +import cn.stylefeng.roses.kernel.auth.api.context.LoginContext; +import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; +import cn.stylefeng.roses.kernel.auth.api.exception.enums.AuthExceptionEnum; +import cn.stylefeng.roses.kernel.auth.api.expander.AuthConfigExpander; +import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginRequest; +import cn.stylefeng.roses.kernel.auth.api.pojo.auth.LoginResponse; +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; +import cn.stylefeng.roses.kernel.jwt.api.context.JwtContext; +import cn.stylefeng.roses.kernel.jwt.api.exception.JwtException; +import cn.stylefeng.roses.kernel.jwt.api.pojo.payload.DefaultJwtPayload; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceDefinition; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceUrlParam; +import cn.stylefeng.roses.kernel.rule.util.HttpServletUtil; +import cn.stylefeng.roses.kernel.system.ResourceServiceApi; +import cn.stylefeng.roses.kernel.system.UserServiceApi; +import cn.stylefeng.roses.kernel.system.enums.UserStatusEnum; +import cn.stylefeng.roses.kernel.system.pojo.user.UserLoginInfoDTO; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; + +import static cn.stylefeng.roses.kernel.auth.api.exception.enums.AuthExceptionEnum.RESOURCE_DEFINITION_ERROR; +import static cn.stylefeng.roses.kernel.auth.api.exception.enums.AuthExceptionEnum.TOKEN_ERROR; + +/** + * 认证服务的实现 + * + * @author fengshuonan + * @date 2020/10/20 10:25 + */ +@Service +public class AuthServiceImpl implements AuthServiceApi { + + /** + * 用于操作缓存时候加锁 + */ + private static final Object SESSION_OPERATE_LOCK = new Object(); + + @Resource + private UserServiceApi userServiceApi; + + @Resource + private SessionManagerApi sessionManagerApi; + + @Resource + private ResourceServiceApi resourceServiceApi; + + @Override + public LoginResponse login(LoginRequest loginRequest) { + return loginAction(loginRequest, true); + } + + @Override + public LoginResponse loginWithUserName(String username) { + LoginRequest loginRequest = new LoginRequest(); + loginRequest.setAccount(username); + return loginAction(new LoginRequest(), false); + } + + @Override + public void logout() { + String token = LoginContext.me().getToken(); + logoutWithToken(token); + } + + @Override + public void logoutWithToken(String token) { + // 清除token缓存的用户信息 + sessionManagerApi.removeSession(token); + } + + @Override + public void validateToken(String token) throws AuthException { + try { + JwtContext.me().validateTokenWithException(token); + } catch (JwtException e) { + throw new AuthException(e.getErrorCode(), e.getUserTip()); + } + } + + @Override + public boolean getTokenFlag(String token) { + return JwtContext.me().validateToken(token); + } + + @Override + public void checkAuth(String token, String requestUrl) { + + // 获取url对应的资源信息ResourceDefinition + ResourceUrlParam resourceUrlReq = new ResourceUrlParam(); + resourceUrlReq.setUrl(requestUrl); + ResourceDefinition resourceDefinition = resourceServiceApi.getResourceByUrl(resourceUrlReq); + + // 获取token对应的用户信息 + LoginUser session = sessionManagerApi.getSession(token); + if (session == null) { + throw new AuthException(TOKEN_ERROR); + } + + // 资源为空,直接响应异常,禁止用户访问 + if (resourceDefinition == null) { + throw new AuthException(RESOURCE_DEFINITION_ERROR); + } + + // 检查接口是否需要鉴权 + Boolean requiredLogin = resourceDefinition.getRequiredLogin(); + + // 需要鉴权,则判断token是否过期 + if (requiredLogin) { + this.validateToken(token); + } + } + + /** + * 登录的真正业务逻辑 + * + * @param loginRequest 登录参数 + * @param validatePassword 是否校验密码,true-校验密码,false-不会校验密码 + * @author fengshuonan + * @date 2020/10/21 16:59 + */ + private LoginResponse loginAction(LoginRequest loginRequest, Boolean validatePassword) { + + // 1.参数为空校验 + if (validatePassword) { + if (loginRequest == null || StrUtil.hasBlank(loginRequest.getAccount(), loginRequest.getPassword())) { + throw new AuthException(AuthExceptionEnum.PARAM_EMPTY); + } + } else { + if (loginRequest == null || StrUtil.hasBlank(loginRequest.getAccount())) { + throw new AuthException(AuthExceptionEnum.ACCOUNT_IS_BLANK); + } + } + + // 2.获取用户密码的加密值和用户的状态 + UserLoginInfoDTO userValidateInfo = userServiceApi.getUserLoginInfo(loginRequest.getAccount()); + + // 3.校验用户密码是否正确(BCrypt算法) + if (validatePassword) { + if (ObjectUtil.isEmpty(userValidateInfo.getUserPasswordHexed()) || !BCrypt.checkpw(loginRequest.getPassword(), userValidateInfo.getUserPasswordHexed())) { + throw new AuthException(AuthExceptionEnum.USERNAME_PASSWORD_ERROR); + } + } + + // 4.校验用户是否异常(不是正常状态) + if (!UserStatusEnum.ENABLE.getCode().equals(userValidateInfo.getUserStatus())) { + String userTip = StrUtil.format(AuthExceptionEnum.USER_STATUS_ERROR.getErrorCode(), UserStatusEnum.getCodeMessage(userValidateInfo.getUserStatus())); + throw new AuthException(AuthExceptionEnum.USER_STATUS_ERROR.getErrorCode(), userTip); + } + + // 5.获取LoginUser,用于用户的缓存 + LoginUser loginUser = userValidateInfo.getLoginUser(); + + // 6.生成用户的token + DefaultJwtPayload defaultJwtPayload = new DefaultJwtPayload(loginUser.getId(), loginUser.getAccount(), loginRequest.getRememberMe()); + String jwtToken = JwtContext.me().generateTokenDefaultPayload(defaultJwtPayload); + + synchronized (SESSION_OPERATE_LOCK) { + + // 7.缓存用户信息,创建会话 + sessionManagerApi.createSession(jwtToken, loginUser); + + // 8.如果开启了单账号单端在线,则踢掉已经上线的该用户 + if (AuthConfigExpander.getSingleAccountLoginFlag()) { + sessionManagerApi.removeSessionExcludeToken(jwtToken); + } + + } + + // 9.更新用户登录时间和ip + String ip = HttpServletUtil.getRequestClientIp(HttpServletUtil.getRequest()); + userServiceApi.updateUserLoginInfo(loginUser.getId(), new Date(), ip); + + // 10.组装返回结果 + return new LoginResponse(loginUser, jwtToken); + } + +} diff --git a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/LoginUserImpl.java b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/LoginUserImpl.java new file mode 100644 index 000000000..4c1e67b31 --- /dev/null +++ b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/auth/LoginUserImpl.java @@ -0,0 +1,142 @@ +package cn.stylefeng.roses.kernel.auth.auth; + +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.auth.api.LoginUserApi; +import cn.stylefeng.roses.kernel.auth.api.SessionManagerApi; +import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; +import cn.stylefeng.roses.kernel.auth.api.expander.AuthConfigExpander; +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; +import cn.stylefeng.roses.kernel.rule.pojo.dict.SimpleDict; +import cn.stylefeng.roses.kernel.rule.util.HttpServletUtil; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.Set; + +import static cn.stylefeng.roses.kernel.auth.api.exception.enums.AuthExceptionEnum.TOKEN_GET_ERROR; + +/** + * 当前登陆用户的接口实现 + * + * @author fengshuonan + * @date 2020/10/21 18:09 + */ +@Service +public class LoginUserImpl implements LoginUserApi { + + @Resource + private SessionManagerApi sessionManagerApi; + + @Override + public String getToken() { + + // 获取当前http请求 + HttpServletRequest request = HttpServletUtil.getRequest(); + + // 优先从param参数中获取token + String parameterToken = request.getParameter(AuthConfigExpander.getAuthTokenParamName()); + + // 不为空则直接返回param的token + if (StrUtil.isNotBlank(parameterToken)) { + return parameterToken; + } + + // 从header中获取token + String authToken = request.getHeader(AuthConfigExpander.getAuthTokenHeaderName()); + if (StrUtil.isNotBlank(authToken)) { + return authToken; + } + + // 获取不到token,直接告诉用户 + throw new AuthException(TOKEN_GET_ERROR); + } + + @Override + public LoginUser getLoginUser() throws AuthException { + + // 获取用户的token + String token = getToken(); + + // 获取session中该token对应的用户 + LoginUser session = sessionManagerApi.getSession(token); + + // session为空抛出异常 + if (session == null) { + throw new AuthException(TOKEN_GET_ERROR); + } + + return session; + } + + @Override + public LoginUser getLoginUserNullable() { + + // 获取用户的token + String token = null; + try { + token = getToken(); + } catch (Exception e) { + return null; + } + + // 获取session中该token对应的用户 + return sessionManagerApi.getSession(token); + + } + + @Override + public boolean getSuperAdminFlag() { + LoginUser loginUser = getLoginUser(); + return loginUser.getSuperAdmin(); + } + + @Override + public boolean hasLogin() { + + // 获取用户的token + String token = null; + try { + token = getToken(); + } catch (Exception e) { + return false; + } + + // 获取是否在会话中有 + return sessionManagerApi.haveSession(token); + } + + @Override + public boolean hasPermission(String requestUri) { + + LoginUser loginUser = getLoginUser(); + + Set resourceUrls = loginUser.getResourceUrls(); + + if (resourceUrls != null && resourceUrls.size() > 0) { + return resourceUrls.contains(requestUri); + } else { + return false; + } + + } + + @Override + public boolean hasRole(String roleCode) { + + LoginUser loginUser = getLoginUser(); + + Set roles = loginUser.getRoles(); + + if (roles != null && roles.size() > 0) { + for (SimpleDict role : roles) { + if (role.getCode().equals(roleCode)) { + return true; + } + } + } + + return false; + } + +} diff --git a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/permission/PermissionServiceImpl.java b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/permission/PermissionServiceImpl.java new file mode 100644 index 000000000..ba11598da --- /dev/null +++ b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/permission/PermissionServiceImpl.java @@ -0,0 +1,63 @@ +package cn.stylefeng.roses.kernel.auth.permission; + +import cn.stylefeng.roses.kernel.auth.api.PermissionServiceApi; +import cn.stylefeng.roses.kernel.auth.api.SessionManagerApi; +import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceDefinition; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceUrlParam; +import cn.stylefeng.roses.kernel.system.ResourceServiceApi; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Set; + +import static cn.stylefeng.roses.kernel.auth.api.exception.enums.AuthExceptionEnum.*; + +/** + * 权限相关的service + * + * @author fengshuonan + * @date 2020/10/22 15:49 + */ +@Service +public class PermissionServiceImpl implements PermissionServiceApi { + + @Resource + private ResourceServiceApi resourceServiceApi; + + @Resource + private SessionManagerApi sessionManagerApi; + + @Override + public void checkPermission(String token, String requestUrl) { + + // 获取token对应的用户信息 + LoginUser session = sessionManagerApi.getSession(token); + if (session == null) { + throw new AuthException(TOKEN_ERROR); + } + + // 获取url对应的资源信息ResourceDefinition + ResourceUrlParam resourceUrlReq = new ResourceUrlParam(); + resourceUrlReq.setUrl(requestUrl); + ResourceDefinition resourceDefinition = resourceServiceApi.getResourceByUrl(resourceUrlReq); + + // 资源为空,直接响应异常,禁止用户访问 + if (resourceDefinition == null) { + throw new AuthException(RESOURCE_DEFINITION_ERROR); + } + + // 检查接口是否需要权限验证 + Boolean requiredPermission = resourceDefinition.getRequiredPermission(); + + // 需要权限认证,验证用户有没有当前url的权限 + if (requiredPermission) { + Set resourceUrls = session.getResourceUrls(); + if (resourceUrls == null || resourceUrls.size() == 0) { + throw new AuthException(PERMISSION_RES_VALIDATE_ERROR); + } + } + } + +} diff --git a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/session/MemoryCacheSessionManager.java b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/session/MemoryCacheSessionManager.java new file mode 100644 index 000000000..d4ad44059 --- /dev/null +++ b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/session/MemoryCacheSessionManager.java @@ -0,0 +1,146 @@ +package cn.stylefeng.roses.kernel.auth.session; + + +import cn.hutool.cache.impl.TimedCache; +import cn.stylefeng.roses.kernel.auth.api.SessionManagerApi; +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static cn.stylefeng.roses.kernel.auth.api.constants.AuthConstants.LOGGED_TOKEN_PREFIX; +import static cn.stylefeng.roses.kernel.auth.api.constants.AuthConstants.LOGGED_USERID_PREFIX; + +/** + * 基于内存的会话管理 + * + * @author fengshuonan + * @date 2019-09-28-14:43 + */ +public class MemoryCacheSessionManager implements SessionManagerApi { + + /** + * 登录用户缓存 + *

+ * key是 LOGGED_TOKEN_PREFIX + 用户的token + */ + private final TimedCache loginUserCache; + + /** + * 用户token的缓存,这个缓存用来存储一个用户的所有token + *

+ * 没开启单端限制的话,一个用户可能有多个token,因为一个用户可能在多个地点或打开多个浏览器访问系统 + *

+ * key是 LOGGED_USERID_PREFIX + 用户id + *

+ * 这个缓存应该定时刷新下,因为有过期token的用户,所以这个里边的值set得清理 + */ + private final Map> loginTokenCache = new HashMap<>(); + + public MemoryCacheSessionManager(TimedCache loginUserCache) { + this.loginUserCache = loginUserCache; + } + + @Override + public void createSession(String token, LoginUser loginUser) { + + // 装配用户信息的缓存 + loginUserCache.put(getTokenKey(token), loginUser); + + // 装配用户token的缓存 + String userIdKey = getUserIdKey(loginUser.getId()); + Set theUserTokens = loginTokenCache.get(userIdKey); + if (theUserTokens == null) { + HashSet tempUserTokens = new HashSet<>(); + tempUserTokens.add(token); + loginTokenCache.put(userIdKey, tempUserTokens); + } else { + theUserTokens.add(token); + } + } + + @Override + public LoginUser getSession(String token) { + return loginUserCache.get(getTokenKey(token)); + } + + @Override + public void removeSession(String token) { + + String tokenKey = getTokenKey(token); + LoginUser loginUser = loginUserCache.get(tokenKey); + + // 删除用户id对应token的缓存 + if (loginUser != null) { + Long userId = loginUser.getId(); + Set userTokens = loginTokenCache.get(getUserIdKey(userId)); + if (userTokens != null) { + userTokens.remove(token); + + // 如果删除后size为0,则把整个key删掉 + if (userTokens.size() == 0) { + loginTokenCache.remove(getUserIdKey(userId)); + } + } + } + + // 删除用户信息的缓存 + loginUserCache.remove(getTokenKey(token)); + } + + @Override + public void removeSessionExcludeToken(String token) { + + // 获取token对应的会话 + LoginUser session = this.getSession(token); + + // 如果会话为空,直接返回 + if (session == null) { + return; + } + + // 获取用户id + Long userId = session.getId(); + + // 设置用户id对应的token列表为参数token + HashSet tokenSet = new HashSet<>(); + tokenSet.add(token); + loginTokenCache.put(getUserIdKey(userId), tokenSet); + } + + @Override + public boolean haveSession(String token) { + return loginUserCache.containsKey(getTokenKey(token)); + } + + @Override + public void refreshSession(String token) { + LoginUser loginUser = loginUserCache.get(getTokenKey(token)); + if (loginUser != null) { + loginUserCache.put(LOGGED_TOKEN_PREFIX + token, loginUser, loginUserCache.timeout()); + } + } + + /** + * 获取token的缓存key + * + * @author fengshuonan + * @date 2020/10/21 15:09 + */ + private String getTokenKey(String token) { + return LOGGED_TOKEN_PREFIX + token; + } + + /** + * 获取用户id的缓存key + * + * @author fengshuonan + * @date 2020/10/21 15:10 + */ + private String getUserIdKey(Long userId) { + return LOGGED_USERID_PREFIX + userId; + } + +} diff --git a/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/session/RedisSessionManager.java b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/session/RedisSessionManager.java new file mode 100644 index 000000000..09efc9ba4 --- /dev/null +++ b/kernel-d-auth/auth-sdk/src/main/java/cn/stylefeng/roses/kernel/auth/session/RedisSessionManager.java @@ -0,0 +1,159 @@ +package cn.stylefeng.roses.kernel.auth.session; + +import com.alibaba.fastjson.parser.ParserConfig; +import cn.stylefeng.roses.kernel.auth.api.SessionManagerApi; +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; +import org.springframework.data.redis.core.RedisTemplate; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static cn.stylefeng.roses.kernel.auth.api.constants.AuthConstants.LOGGED_TOKEN_PREFIX; +import static cn.stylefeng.roses.kernel.auth.api.constants.AuthConstants.LOGGED_USERID_PREFIX; + +/** + * 基于redis的会话管理 + * + * @author fengshuonan + * @date 2019-09-28-14:43 + */ +public class RedisSessionManager implements SessionManagerApi { + + /** + * 登录用户缓存 + *

+ * key是 LOGGED_TOKEN_PREFIX + 用户的token + */ + private final RedisTemplate loginUserRedisTemplate; + + /** + * 用户token的缓存,这个缓存用来存储一个用户的所有token + *

+ * 没开启单端限制的话,一个用户可能有多个token,因为一个用户可能在多个地点或打开多个浏览器访问系统 + *

+ * key是 LOGGED_USERID_PREFIX + 用户id + *

+ * 这个缓存应该定时刷新下,因为有过期token的用户,所以这个里边的值set得清理 + */ + private final RedisTemplate> loginTokenRedisTemplate; + + /** + * session的超时时间 + */ + private final Long sessionExpiredSeconds; + + public RedisSessionManager(RedisTemplate loginUserRedisTemplate, RedisTemplate> loginTokenRedisTemplate, Long sessionExpiredSeconds) { + this.loginUserRedisTemplate = loginUserRedisTemplate; + this.loginTokenRedisTemplate = loginTokenRedisTemplate; + this.sessionExpiredSeconds = sessionExpiredSeconds; + } + + @Override + public void createSession(String token, LoginUser loginUser) { + ParserConfig.getGlobalInstance().setAutoTypeSupport(true); + + // 装配用户信息的缓存 + loginUserRedisTemplate.opsForValue().set(getTokenKey(token), loginUser, sessionExpiredSeconds, TimeUnit.SECONDS); + + // 装配用户token的缓存 + String userIdKey = getUserIdKey(loginUser.getId()); + Set theUserTokens = loginTokenRedisTemplate.opsForValue().get(userIdKey); + if (theUserTokens == null) { + HashSet tempUserTokens = new HashSet<>(); + tempUserTokens.add(token); + loginTokenRedisTemplate.opsForValue().set(userIdKey, tempUserTokens); + } else { + theUserTokens.add(token); + } + } + + @Override + public LoginUser getSession(String token) { + ParserConfig.getGlobalInstance().setAutoTypeSupport(true); + return loginUserRedisTemplate.opsForValue().get(getTokenKey(token)); + } + + @Override + public void removeSession(String token) { + + String tokenKey = getTokenKey(token); + LoginUser loginUser = loginUserRedisTemplate.opsForValue().get(tokenKey); + + // 删除用户id对应token的缓存 + if (loginUser != null) { + Long userId = loginUser.getId(); + Set userTokens = loginTokenRedisTemplate.opsForValue().get(getUserIdKey(userId)); + if (userTokens != null) { + userTokens.remove(token); + + // 如果删除后size为0,则把整个key删掉 + if (userTokens.size() == 0) { + loginUserRedisTemplate.delete(getUserIdKey(userId)); + } + } + } + + // 删除用户信息的缓存 + loginUserRedisTemplate.delete(getTokenKey(token)); + } + + @Override + public void removeSessionExcludeToken(String token) { + + // 获取token对应的会话 + LoginUser session = this.getSession(token); + + // 如果会话为空,直接返回 + if (session == null) { + return; + } + + // 获取用户id + Long userId = session.getId(); + + // 设置用户id对应的token列表为参数token + HashSet tokenSet = new HashSet<>(); + tokenSet.add(token); + loginTokenRedisTemplate.opsForValue().set(getUserIdKey(userId), tokenSet); + } + + @Override + public boolean haveSession(String token) { + Boolean flag = loginUserRedisTemplate.hasKey(getTokenKey(token)); + if (flag == null) { + return false; + } else { + return flag; + } + } + + @Override + public void refreshSession(String token) { + LoginUser loginUser = loginUserRedisTemplate.boundValueOps(getTokenKey(token)).get(); + if (loginUser != null) { + loginUserRedisTemplate.boundValueOps(getTokenKey(token)).expire(sessionExpiredSeconds, TimeUnit.SECONDS); + } + } + + /** + * 获取token的缓存key + * + * @author fengshuonan + * @date 2020/10/21 15:09 + */ + private String getTokenKey(String token) { + return LOGGED_TOKEN_PREFIX + token; + } + + /** + * 获取用户id的缓存key + * + * @author fengshuonan + * @date 2020/10/21 15:10 + */ + private String getUserIdKey(Long userId) { + return LOGGED_USERID_PREFIX + userId; + } + +} diff --git a/kernel-d-auth/auth-spring-boot-starter/README.md b/kernel-d-auth/auth-spring-boot-starter/README.md new file mode 100644 index 000000000..df3abf5ad --- /dev/null +++ b/kernel-d-auth/auth-spring-boot-starter/README.md @@ -0,0 +1 @@ +认证和鉴权的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-auth/auth-spring-boot-starter/pom.xml b/kernel-d-auth/auth-spring-boot-starter/pom.xml new file mode 100644 index 000000000..c9e70a32f --- /dev/null +++ b/kernel-d-auth/auth-spring-boot-starter/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-auth + 1.0.0 + ../pom.xml + + + auth-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + auth-sdk + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-auth/auth-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/auth/starter/GunsAuthAutoConfiguration.java b/kernel-d-auth/auth-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/auth/starter/GunsAuthAutoConfiguration.java new file mode 100644 index 000000000..6164ad3e3 --- /dev/null +++ b/kernel-d-auth/auth-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/auth/starter/GunsAuthAutoConfiguration.java @@ -0,0 +1,35 @@ +package cn.stylefeng.roses.kernel.auth.starter; + +import cn.hutool.cache.CacheUtil; +import cn.stylefeng.roses.kernel.auth.api.SessionManagerApi; +import cn.stylefeng.roses.kernel.auth.api.expander.AuthConfigExpander; +import cn.stylefeng.roses.kernel.auth.session.MemoryCacheSessionManager; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 认证和鉴权模块的自动配置 + * + * @author fengshuonan + * @date 2020/11/30 22:16 + */ +@Configuration +public class GunsAuthAutoConfiguration { + + /** + * 默认的session缓存为内存缓存,方便启动 + *

+ * 如需替换请在项目中增加一个SessionManagerApi Bean即可 + * + * @author fengshuonan + * @date 2020/11/30 22:17 + */ + @Bean + @ConditionalOnMissingBean(SessionManagerApi.class) + public SessionManagerApi sessionManagerApi() { + Long sessionExpiredSeconds = AuthConfigExpander.getSessionExpiredSeconds(); + return new MemoryCacheSessionManager(CacheUtil.newTimedCache(sessionExpiredSeconds * 1000)); + } + +} diff --git a/kernel-d-auth/auth-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-auth/auth-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..72ace38f6 --- /dev/null +++ b/kernel-d-auth/auth-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.auth.starter.GunsAuthAutoConfiguration \ No newline at end of file diff --git a/kernel-d-auth/pom.xml b/kernel-d-auth/pom.xml new file mode 100644 index 000000000..101d4d776 --- /dev/null +++ b/kernel-d-auth/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-auth + + pom + + + auth-api + auth-sdk + auth-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-cache/README.md b/kernel-d-cache/README.md new file mode 100644 index 000000000..88cd8393f --- /dev/null +++ b/kernel-d-cache/README.md @@ -0,0 +1,7 @@ +系统配置表模块 + +本模块意在建立一块系统配置内存空间,保存一些系统常用的配置,通过在线管理这些配置,可实时维护这些配置 + +多模块之间可通过config-api提供的接口,进行配置的共享和读取 + +在新增一个配置的时候,优先考虑存到config模块,再考虑往application.yml中存 \ No newline at end of file diff --git a/kernel-d-cache/cache-api/README.md b/kernel-d-cache/cache-api/README.md new file mode 100644 index 000000000..81f0f18e4 --- /dev/null +++ b/kernel-d-cache/cache-api/README.md @@ -0,0 +1,3 @@ +缓存模块,提供一些缓存接口 + +缓存模块为了给系统提供缓存功能,更快的执行业务 \ No newline at end of file diff --git a/kernel-d-cache/cache-api/pom.xml b/kernel-d-cache/cache-api/pom.xml new file mode 100644 index 000000000..71cdc51ec --- /dev/null +++ b/kernel-d-cache/cache-api/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-cache + 1.0.0 + ../pom.xml + + + cache-api + + jar + + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-d-cache/cache-api/src/main/java/cn/stylefeng/roses/kernel/cache/api/CacheOperatorApi.java b/kernel-d-cache/cache-api/src/main/java/cn/stylefeng/roses/kernel/cache/api/CacheOperatorApi.java new file mode 100644 index 000000000..e9cfee9ae --- /dev/null +++ b/kernel-d-cache/cache-api/src/main/java/cn/stylefeng/roses/kernel/cache/api/CacheOperatorApi.java @@ -0,0 +1,104 @@ +package cn.stylefeng.roses.kernel.cache.api; + +import java.util.Collection; +import java.util.Map; + +/** + * 缓存操作的基础接口,可以实现不同种缓存实现 + *

+ * 泛型为cache的值类class类型 + * + * @author stylefeng + * @date 2020/7/8 22:02 + */ +public interface CacheOperatorApi { + + /** + * 添加缓存 + * + * @param key 键 + * @param value 值 + * @author stylefeng + * @date 2020/7/8 22:06 + */ + void put(String key, T value); + + /** + * 添加缓存(带过期时间,单位是秒) + * + * @param key 键 + * @param value 值 + * @param timeoutSeconds 过期时间,单位秒 + * @author stylefeng + * @date 2020/7/8 22:07 + */ + void put(String key, T value, Long timeoutSeconds); + + /** + * 通过缓存key获取缓存 + * + * @param key 键 + * @return 值 + * @author stylefeng + * @date 2020/7/8 22:08 + */ + T get(String key); + + /** + * 删除缓存 + * + * @param key 键,多个 + * @author stylefeng + * @date 2020/7/8 22:09 + */ + void remove(String... key); + + /** + * 判断某个key值是否存在于缓存 + * + * @param key 缓存的键 + * @return true-存在,false-不存在 + * @author fengshuonan + * @date 2020/11/20 16:50 + */ + boolean contains(String key); + + /** + * 获得缓存的所有key列表(不带common prefix的) + * + * @return key列表 + * @author stylefeng + * @date 2020/7/8 22:11 + */ + Collection getAllKeys(); + + /** + * 获得缓存的所有值列表 + * + * @return 值列表 + * @author stylefeng + * @date 2020/7/8 22:11 + */ + Collection getAllValues(); + + /** + * 获取所有的key,value + * + * @return 键值map + * @author stylefeng + * @date 2020/7/8 22:11 + */ + Map getAllKeyValues(); + + /** + * 通用缓存的前缀,用于区分不同业务 + *

+ * 如果带了前缀,所有的缓存在添加的时候,key都会带上这个前缀 + * + * @return 缓存前缀 + * @author stylefeng + * @date 2020/7/9 11:06 + */ + String getCommonKeyPrefix(); + +} diff --git a/kernel-d-cache/cache-api/src/main/java/cn/stylefeng/roses/kernel/cache/api/constants/CacheConstants.java b/kernel-d-cache/cache-api/src/main/java/cn/stylefeng/roses/kernel/cache/api/constants/CacheConstants.java new file mode 100644 index 000000000..a5e629222 --- /dev/null +++ b/kernel-d-cache/cache-api/src/main/java/cn/stylefeng/roses/kernel/cache/api/constants/CacheConstants.java @@ -0,0 +1,26 @@ +package cn.stylefeng.roses.kernel.cache.api.constants; + +/** + * 缓存模块的常量 + * + * @author fengshuonan + * @date 2020/10/22 16:55 + */ +public interface CacheConstants { + + /** + * 缓存模块的名称 + */ + String CACHE_MODULE_NAME = "kernel-d-cache"; + + /** + * 缓存模块的异常步进值 + */ + String CACHE_EXCEPTION_STEP_CODE = "07"; + + /** + * 缓存的分割符号 + */ + String CACHE_DELIMITER = ":"; + +} diff --git a/kernel-d-cache/cache-sdk-memory/README.md b/kernel-d-cache/cache-sdk-memory/README.md new file mode 100644 index 000000000..c2a5506bc --- /dev/null +++ b/kernel-d-cache/cache-sdk-memory/README.md @@ -0,0 +1 @@ +系统配置表的实现,基于数据库存储 \ No newline at end of file diff --git a/kernel-d-cache/cache-sdk-memory/pom.xml b/kernel-d-cache/cache-sdk-memory/pom.xml new file mode 100644 index 000000000..410386f9d --- /dev/null +++ b/kernel-d-cache/cache-sdk-memory/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-cache + 1.0.0 + ../pom.xml + + + cache-sdk-memory + + jar + + + + + + cn.stylefeng.roses + cache-api + 1.0.0 + + + + + diff --git a/kernel-d-cache/cache-sdk-memory/src/main/java/cn/stylefeng/roses/kernel/cache/AbstractMemoryCacheOperator.java b/kernel-d-cache/cache-sdk-memory/src/main/java/cn/stylefeng/roses/kernel/cache/AbstractMemoryCacheOperator.java new file mode 100644 index 000000000..83d9a2a0f --- /dev/null +++ b/kernel-d-cache/cache-sdk-memory/src/main/java/cn/stylefeng/roses/kernel/cache/AbstractMemoryCacheOperator.java @@ -0,0 +1,86 @@ +package cn.stylefeng.roses.kernel.cache; + +import cn.hutool.cache.impl.CacheObj; +import cn.hutool.cache.impl.TimedCache; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.cache.api.CacheOperatorApi; + +import java.util.*; + +/** + * 基于内存的缓存封装 + * + * @author stylefeng + * @date 2020/7/9 10:09 + */ +public abstract class AbstractMemoryCacheOperator implements CacheOperatorApi { + + private final TimedCache timedCache; + + public AbstractMemoryCacheOperator(TimedCache timedCache) { + this.timedCache = timedCache; + } + + @Override + public void put(String key, T value) { + timedCache.put(getCommonKeyPrefix() + key, value); + } + + @Override + public void put(String key, T value, Long timeoutSeconds) { + timedCache.put(getCommonKeyPrefix() + key, value, timeoutSeconds * 1000); + } + + @Override + public T get(String key) { + // 如果用户在超时前调用了get(key)方法,会重头计算起始时间,false的作用就是不从头算 + return timedCache.get(getCommonKeyPrefix() + key, true); + } + + @Override + public void remove(String... key) { + if (key.length > 0) { + for (String itemKey : key) { + timedCache.remove(getCommonKeyPrefix() + itemKey); + } + } + } + + @Override + public boolean contains(String key) { + return timedCache.containsKey(key); + } + + @Override + public Collection getAllKeys() { + Iterator> cacheObjIterator = timedCache.cacheObjIterator(); + ArrayList keys = CollectionUtil.newArrayList(); + while (cacheObjIterator.hasNext()) { + // 去掉缓存key的common prefix前缀 + String key = cacheObjIterator.next().getKey(); + keys.add(StrUtil.removePrefix(key, getCommonKeyPrefix())); + } + return keys; + } + + @Override + public Collection getAllValues() { + Iterator> cacheObjIterator = timedCache.cacheObjIterator(); + ArrayList values = CollectionUtil.newArrayList(); + while (cacheObjIterator.hasNext()) { + values.add(cacheObjIterator.next().getValue()); + } + return values; + } + + @Override + public Map getAllKeyValues() { + Collection allKeys = this.getAllKeys(); + HashMap results = CollectionUtil.newHashMap(); + for (String key : allKeys) { + results.put(key, this.get(key)); + } + return results; + } +} diff --git a/kernel-d-cache/cache-sdk-redis/README.md b/kernel-d-cache/cache-sdk-redis/README.md new file mode 100644 index 000000000..c2a5506bc --- /dev/null +++ b/kernel-d-cache/cache-sdk-redis/README.md @@ -0,0 +1 @@ +系统配置表的实现,基于数据库存储 \ No newline at end of file diff --git a/kernel-d-cache/cache-sdk-redis/pom.xml b/kernel-d-cache/cache-sdk-redis/pom.xml new file mode 100644 index 000000000..d971825fe --- /dev/null +++ b/kernel-d-cache/cache-sdk-redis/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-cache + 1.0.0 + ../pom.xml + + + cache-sdk-redis + + jar + + + + + + cn.stylefeng.roses + cache-api + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + io.lettuce + lettuce-core + + + + + redis.clients + jedis + + + + + diff --git a/kernel-d-cache/cache-sdk-redis/src/main/java/cn/stylefeng/roses/kernel/cache/AbstractRedisCacheOperator.java b/kernel-d-cache/cache-sdk-redis/src/main/java/cn/stylefeng/roses/kernel/cache/AbstractRedisCacheOperator.java new file mode 100644 index 000000000..fd5788f97 --- /dev/null +++ b/kernel-d-cache/cache-sdk-redis/src/main/java/cn/stylefeng/roses/kernel/cache/AbstractRedisCacheOperator.java @@ -0,0 +1,85 @@ +package cn.stylefeng.roses.kernel.cache; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.cache.api.CacheOperatorApi; +import org.springframework.data.redis.core.RedisTemplate; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + + +/** + * 基于redis的缓存封装 + * + * @author stylefeng + * @date 2020/7/9 10:09 + */ +public abstract class AbstractRedisCacheOperator implements CacheOperatorApi { + + private final RedisTemplate redisTemplate; + + public AbstractRedisCacheOperator(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + @Override + public void put(String key, T value) { + redisTemplate.boundValueOps(getCommonKeyPrefix() + key).set(value); + } + + @Override + public void put(String key, T value, Long timeoutSeconds) { + redisTemplate.boundValueOps(getCommonKeyPrefix() + key).set(value, timeoutSeconds, TimeUnit.SECONDS); + } + + @Override + public T get(String key) { + return redisTemplate.boundValueOps(getCommonKeyPrefix() + key).get(); + } + + @Override + public void remove(String... key) { + ArrayList keys = CollectionUtil.toList(key); + List withPrefixKeys = keys.stream().map(i -> getCommonKeyPrefix() + i).collect(Collectors.toList()); + redisTemplate.delete(withPrefixKeys); + } + + @Override + public boolean contains(String key) { + T value = redisTemplate.boundValueOps(getCommonKeyPrefix() + key).get(); + return value != null; + } + + @Override + public Collection getAllKeys() { + Set keys = redisTemplate.keys(getCommonKeyPrefix() + "*"); + if (keys != null) { + // 去掉缓存key的common prefix前缀 + return keys.stream().map(key -> StrUtil.removePrefix(key, getCommonKeyPrefix())).collect(Collectors.toSet()); + } else { + return CollectionUtil.newHashSet(); + } + } + + @Override + public Collection getAllValues() { + Set keys = redisTemplate.keys(getCommonKeyPrefix() + "*"); + if (keys != null) { + return redisTemplate.opsForValue().multiGet(keys); + } else { + return CollectionUtil.newArrayList(); + } + } + + @Override + public Map getAllKeyValues() { + Collection allKeys = this.getAllKeys(); + HashMap results = CollectionUtil.newHashMap(); + for (String key : allKeys) { + results.put(key, this.get(key)); + } + return results; + } +} diff --git a/kernel-d-cache/pom.xml b/kernel-d-cache/pom.xml new file mode 100644 index 000000000..014f7f1a2 --- /dev/null +++ b/kernel-d-cache/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-cache + + pom + + + cache-api + cache-sdk-memory + cache-sdk-redis + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-config/README.md b/kernel-d-config/README.md new file mode 100644 index 000000000..88cd8393f --- /dev/null +++ b/kernel-d-config/README.md @@ -0,0 +1,7 @@ +系统配置表模块 + +本模块意在建立一块系统配置内存空间,保存一些系统常用的配置,通过在线管理这些配置,可实时维护这些配置 + +多模块之间可通过config-api提供的接口,进行配置的共享和读取 + +在新增一个配置的时候,优先考虑存到config模块,再考虑往application.yml中存 \ No newline at end of file diff --git a/kernel-d-config/config-api/README.md b/kernel-d-config/config-api/README.md new file mode 100644 index 000000000..f852498ba --- /dev/null +++ b/kernel-d-config/config-api/README.md @@ -0,0 +1 @@ +配置模块的api模块 \ No newline at end of file diff --git a/kernel-d-config/config-api/pom.xml b/kernel-d-config/config-api/pom.xml new file mode 100644 index 000000000..108b36cee --- /dev/null +++ b/kernel-d-config/config-api/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-config + 1.0.0 + ../pom.xml + + + config-api + + jar + + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/ConfigApi.java b/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/ConfigApi.java new file mode 100644 index 000000000..868c3a544 --- /dev/null +++ b/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/ConfigApi.java @@ -0,0 +1,101 @@ +package cn.stylefeng.roses.kernel.config.api; + +import cn.stylefeng.roses.kernel.config.api.exception.ConfigException; + +import java.util.Map; +import java.util.Set; + +/** + * 系统配置表相关的api + *

+ * 系统配置表的实现可以用内存,数据库或redis + * + * @author fengshuonan + * @date 2020/10/17 10:27 + */ +public interface ConfigApi { + + /** + * 初始化配置表中的所有配置 + * + * @param configs 配置表的所有配置 + * @author fengshuonan + * @date 2020/10/17 14:11 + */ + void initConfig(Map configs); + + /** + * 获取配置表中所有配置 + * + * @return 系统配置表中所有的配置 + * @author fengshuonan + * @date 2020/10/17 14:07 + */ + Map getAllConfigs(); + + /** + * 获取所有配置的名称集合 + * + * @return 所有配置的名称 + * @author fengshuonan + * @date 2020/10/17 14:31 + */ + Set getAllConfigKeys(); + + /** + * 往配置表中添加一个配置 + *

+ * 如果有某个配置,则会覆盖某个配置 + * + * @param key 配置标识 + * @param value 配置具体值 + * @author fengshuonan + * @date 2020/10/17 14:14 + */ + void putConfig(String key, Object value); + + /** + * 删除一个配置项 + * + * @param key 配置名称 + * @author fengshuonan + * @date 2020/10/17 18:45 + */ + void deleteConfig(String key); + + /** + * 获取config表中的配置,如果为空,抛出异常 + * + * @param configCode 变量名称,对应sys_config表中的code + * @param clazz 返回变量值的类型 + * @return 配置的值 + * @throws ConfigException 如果值为空抛出异常会 + * @author stylefeng + * @date 2020/6/20 22:03 + */ + T getConfigValue(String configCode, Class clazz) throws ConfigException; + + /** + * 获取config表中的配置,如果为空,返回null + * + * @param configCode 变量名称,对应sys_config表中的code + * @param clazz 返回变量值的类型 + * @return 配置的值 + * @author stylefeng + * @date 2020/6/20 22:03 + */ + T getConfigValueNullable(String configCode, Class clazz); + + /** + * 获取config表中的配置,如果为空返回默认值 + * + * @param configCode 变量名称,对应sys_config表中的code + * @param clazz 返回变量值的类型 + * @param defaultValue 如果结果为空返回此默认值 + * @return 配置的值 + * @author stylefeng + * @date 2020/6/20 22:03 + */ + T getSysConfigValueWithDefault(String configCode, Class clazz, T defaultValue); + +} diff --git a/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/constants/ConfigConstants.java b/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/constants/ConfigConstants.java new file mode 100644 index 000000000..d02ae1b36 --- /dev/null +++ b/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/constants/ConfigConstants.java @@ -0,0 +1,21 @@ +package cn.stylefeng.roses.kernel.config.api.constants; + +/** + * 系统配置表的常量 + * + * @author fengshuonan + * @date 2020/10/16 11:05 + */ +public interface ConfigConstants { + + /** + * config模块的名称 + */ + String CONFIG_MODULE_NAME = "kernel-d-config"; + + /** + * 异常枚举的步进值 + */ + String CONFIG_EXCEPTION_STEP_CODE = "04"; + +} diff --git a/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/context/ConfigContext.java b/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/context/ConfigContext.java new file mode 100644 index 000000000..595081f80 --- /dev/null +++ b/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/context/ConfigContext.java @@ -0,0 +1,45 @@ +package cn.stylefeng.roses.kernel.config.api.context; + +import cn.stylefeng.roses.kernel.config.api.ConfigApi; +import cn.stylefeng.roses.kernel.config.api.exception.ConfigException; + +import static cn.stylefeng.roses.kernel.config.api.exception.enums.ConfigExceptionEnum.CONFIG_CONTAINER_IS_NULL; + +/** + * 系统配置表相关的api + *

+ * 系统配置表默认由数据库实现,可实现在线管理,也可以拓展redis等实现 + *

+ * 使用之前请调用setConfigApi初始化 + * + * @author fengshuonan + * @date 2020/10/17 10:27 + */ +public class ConfigContext { + + private static ConfigApi CONFIG_API = null; + + /** + * 获取config操作接口 + * + * @author fengshuonan + * @date 2020/10/17 14:30 + */ + public static ConfigApi me() { + if (CONFIG_API == null) { + throw new ConfigException(CONFIG_CONTAINER_IS_NULL); + } + return CONFIG_API; + } + + /** + * 设置config api的实现 + * + * @author fengshuonan + * @date 2020/12/4 14:35 + */ + public static void setConfigApi(ConfigApi configApi) { + CONFIG_API = configApi; + } + +} \ No newline at end of file diff --git a/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/exception/ConfigException.java b/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/exception/ConfigException.java new file mode 100644 index 000000000..9792d2fcd --- /dev/null +++ b/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/exception/ConfigException.java @@ -0,0 +1,27 @@ +package cn.stylefeng.roses.kernel.config.api.exception; + +import cn.stylefeng.roses.kernel.config.api.constants.ConfigConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; + +/** + * 系统配置表的异常 + * + * @author fengshuonan + * @date 2020/10/15 15:59 + */ +public class ConfigException extends ServiceException { + + public ConfigException(String errorCode, String userTip) { + super(ConfigConstants.CONFIG_MODULE_NAME, errorCode, userTip); + } + + public ConfigException(AbstractExceptionEnum exception) { + super(ConfigConstants.CONFIG_MODULE_NAME, exception); + } + + public ConfigException(AbstractExceptionEnum exception, String userTip) { + super(ConfigConstants.CONFIG_MODULE_NAME, exception.getErrorCode(), userTip); + } + +} diff --git a/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/exception/enums/ConfigExceptionEnum.java b/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/exception/enums/ConfigExceptionEnum.java new file mode 100644 index 000000000..273c2dfb8 --- /dev/null +++ b/kernel-d-config/config-api/src/main/java/cn/stylefeng/roses/kernel/config/api/exception/enums/ConfigExceptionEnum.java @@ -0,0 +1,81 @@ +package cn.stylefeng.roses.kernel.config.api.exception.enums; + +import cn.stylefeng.roses.kernel.config.api.constants.ConfigConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import lombok.Getter; + +/** + * 系统配置表相关的异常枚举 + * + * @author fengshuonan + * @date 2020/10/16 10:53 + */ +@Getter +public enum ConfigExceptionEnum implements AbstractExceptionEnum { + + /** + * 数据库操作未知异常 + */ + DAO_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ConfigConstants.CONFIG_EXCEPTION_STEP_CODE + "01", "配置表信息操作异常"), + + /** + * 系统配置表不存在该配置 + *

+ * 使用时候,用StrUtil.format()将配置名称带上 + */ + CONFIG_NOT_EXIST(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ConfigConstants.CONFIG_EXCEPTION_STEP_CODE + "02", "系统配置表不存在该配置,请检查该配置是否存在,配置名称:{}"), + + /** + * 系统配置表获取值时,强转类型异常 + *

+ * 使用时候,用StrUtil.format()将配置名称带上 + */ + CONVERT_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ConfigConstants.CONFIG_EXCEPTION_STEP_CODE + "03", "获取系统配置值时,强转类型异常,配置名称:{},配置值:{},转化类型:{}"), + + /** + * 获取不到application.yml中的数据库配置 + */ + APP_DB_CONFIG_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ConfigConstants.CONFIG_EXCEPTION_STEP_CODE + "04", "获取不到application.yml中的数据库配置,无法从数据库加载系统配置表"), + + /** + * 初始化系统配置表失败,找不到com.mysql.cj.jdbc.Driver驱动类 + */ + CLASS_NOT_FOUND_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ConfigConstants.CONFIG_EXCEPTION_STEP_CODE + "06", "初始化系统配置表失败,找不到com.mysql.cj.jdbc.Driver驱动类"), + + /** + * 初始化系统配置表失败,执行查询语句失败 + */ + CONFIG_SQL_EXE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ConfigConstants.CONFIG_EXCEPTION_STEP_CODE + "07", "初始化系统配置表失败,执行查询语句失败"), + + /** + * 系统参数配置编码重复 + */ + CONFIG_CODE_REPEAT(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ConfigConstants.CONFIG_EXCEPTION_STEP_CODE + "08", "系统参数配置编码重复,请检查code参数"), + + /** + * 删除失败,不能删除系统参数 + */ + CONFIG_SYS_CAN_NOT_DELETE(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ConfigConstants.CONFIG_EXCEPTION_STEP_CODE + "09", "删除失败,不能删除系统参数"), + + /** + * 配置容器是空,请先初始化配置容器 + */ + CONFIG_CONTAINER_IS_NULL(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ConfigConstants.CONFIG_EXCEPTION_STEP_CODE + "10", "配置容器为空,请先初始化配置容器,请调用ConfigContext.setConfigApi()初始化"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + ConfigExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-config/config-business/README.md b/kernel-d-config/config-business/README.md new file mode 100644 index 000000000..072793752 --- /dev/null +++ b/kernel-d-config/config-business/README.md @@ -0,0 +1 @@ +系统配置表在线维护模块,可以在线管理配置 \ No newline at end of file diff --git a/kernel-d-config/config-business/pom.xml b/kernel-d-config/config-business/pom.xml new file mode 100644 index 000000000..fd96f9662 --- /dev/null +++ b/kernel-d-config/config-business/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-config + 1.0.0 + ../pom.xml + + + config-business + + jar + + + + + + cn.stylefeng.roses + config-sdk-db + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/controller/SysConfigController.java b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/controller/SysConfigController.java new file mode 100644 index 000000000..4240deedd --- /dev/null +++ b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/controller/SysConfigController.java @@ -0,0 +1,101 @@ +package cn.stylefeng.roses.kernel.config.modular.controller; + +import cn.stylefeng.roses.kernel.config.modular.param.SysConfigParam; +import cn.stylefeng.roses.kernel.config.modular.service.SysConfigService; +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + + +/** + * 参数配置控制器 + * + * @author stylefeng + * @date 2020/4/13 22:46 + */ +@RestController +@ApiResource(name = "参数配置控制器") +public class SysConfigController { + + @Resource + private SysConfigService sysConfigService; + + /** + * 分页查询配置列表 + * + * @author fengshuonan + * @date 2020/4/14 11:10 + */ + @GetResource(name = "分页查询配置列表", path = "/sysConfig/page") + public ResponseData page(SysConfigParam sysConfigParam) { + return new SuccessResponseData(sysConfigService.page(sysConfigParam)); + } + + /** + * 系统参数配置列表 + * + * @author fengshuonan + * @date 2020/4/14 11:10 + */ + @GetResource(name = "系统参数配置列表", path = "/sysConfig/list") + public ResponseData list(SysConfigParam sysConfigParam) { + return new SuccessResponseData(sysConfigService.list(sysConfigParam)); + } + + /** + * 查看系统参数配置 + * + * @author fengshuonan + * @date 2020/4/14 11:12 + */ + @GetResource(name = "查看系统参数配置", path = "/sysConfig/detail") + public ResponseData detail(@Validated(SysConfigParam.detail.class) SysConfigParam sysConfigParam) { + return new SuccessResponseData(sysConfigService.detail(sysConfigParam)); + } + + /** + * 添加系统参数配置 + * + * @author fengshuonan + * @date 2020/4/14 11:11 + */ + @PostResource(name = "添加系统参数配置", path = "/sysConfig/add") + public ResponseData add(@RequestBody @Validated(SysConfigParam.add.class) SysConfigParam sysConfigParam) { + sysConfigService.add(sysConfigParam); + return new SuccessResponseData(); + } + + /** + * 删除系统参数配置 + * + * @author fengshuonan + * @date 2020/4/14 11:11 + */ + @PostResource(name = "删除系统参数配置", path = "/sysConfig/delete") + public ResponseData delete(@RequestBody @Validated(SysConfigParam.delete.class) SysConfigParam sysConfigParam) { + sysConfigService.delete(sysConfigParam); + return new SuccessResponseData(); + } + + /** + * 编辑系统参数配置 + * + * @author fengshuonan + * @date 2020/4/14 11:11 + */ + @PostResource(name = "编辑系统参数配置", path = "/sysConfig/edit") + public ResponseData edit(@RequestBody @Validated(SysConfigParam.edit.class) SysConfigParam sysConfigParam) { + sysConfigService.edit(sysConfigParam); + return new SuccessResponseData(); + } + +} + + diff --git a/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/entity/SysConfig.java b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/entity/SysConfig.java new file mode 100644 index 000000000..30a7020ce --- /dev/null +++ b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/entity/SysConfig.java @@ -0,0 +1,102 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.config.modular.entity; + +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 cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 参数配置 + *

+ * + * @author stylefeng + * @date 2019/6/20 13:44 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_config") +public class SysConfig extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 名称 + */ + @TableField("name") + private String name; + + /** + * 编码 + */ + @TableField("code") + private String code; + + /** + * 属性值 + */ + @TableField("value") + private String value; + + /** + * 是否是系统参数(Y-是,N-否) + */ + @TableField("sys_flag") + private String sysFlag; + + /** + * 备注 + */ + @TableField("remark") + private String remark; + + /** + * 状态(字典 1正常 2停用) + */ + @TableField("status") + private Integer status; + + /** + * 常量所属分类的编码,来自于“常量的分类”字典 + */ + @TableField("group_code") + private String groupCode; + + /** + * 是否删除(Y-已删除,N-未删除) + */ + @TableField("del_flag") + private String delFlag; + +} diff --git a/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/listener/ConfigInitListener.java b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/listener/ConfigInitListener.java new file mode 100644 index 000000000..32d491edf --- /dev/null +++ b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/listener/ConfigInitListener.java @@ -0,0 +1,95 @@ +package cn.stylefeng.roses.kernel.config.modular.listener; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.db.DbUtil; +import cn.hutool.db.Entity; +import cn.hutool.db.handler.EntityListHandler; +import cn.hutool.db.sql.SqlExecutor; +import cn.stylefeng.roses.kernel.config.ConfigContainer; +import cn.stylefeng.roses.kernel.config.api.context.ConfigContext; +import cn.stylefeng.roses.kernel.config.api.exception.ConfigException; +import cn.stylefeng.roses.kernel.config.api.exception.enums.ConfigExceptionEnum; +import cn.stylefeng.roses.kernel.rule.enums.StatusEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationContextInitializedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.List; + +import static cn.stylefeng.roses.kernel.config.api.exception.enums.ConfigExceptionEnum.APP_DB_CONFIG_ERROR; + +/** + * 初始化系统配置表 + *

+ * 当spring装配好配置后,就去数据库读constants + *

+ * + * @author stylefeng + * @date 2020/6/6 23:39 + */ +@Slf4j +public class ConfigInitListener implements ApplicationListener, Ordered { + + private static final String CONFIG_LIST_SQL = "select code,value from sys_config where status = ?"; + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + @Override + public void onApplicationEvent(ApplicationContextInitializedEvent applicationContextInitializedEvent) { + + // 如果是配置中心的上下文略过,spring cloud环境environment会读取不到 + ConfigurableApplicationContext applicationContext = applicationContextInitializedEvent.getApplicationContext(); + if (applicationContext instanceof AnnotationConfigApplicationContext) { + return; + } + + // 初始化Config Api + ConfigContext.setConfigApi(new ConfigContainer()); + + ConfigurableEnvironment environment = applicationContextInitializedEvent.getApplicationContext().getEnvironment(); + + // 获取数据库连接配置 + String dataSourceUrl = environment.getProperty("spring.datasource.url"); + String dataSourceUsername = environment.getProperty("spring.datasource.username"); + String dataSourcePassword = environment.getProperty("spring.datasource.password"); + + // 如果有为空的配置,终止执行 + if (ObjectUtil.hasEmpty(dataSourceUrl, dataSourceUsername, dataSourcePassword)) { + throw new ConfigException(APP_DB_CONFIG_ERROR); + } + + Connection conn = null; + try { + Class.forName("com.mysql.cj.jdbc.Driver"); + assert dataSourceUrl != null; + conn = DriverManager.getConnection(dataSourceUrl, dataSourceUsername, dataSourcePassword); + + // 获取sys_config表的数据 + List entityList = SqlExecutor.query(conn, CONFIG_LIST_SQL, new EntityListHandler(), StatusEnum.ENABLE.getCode()); + + // 将查询到的参数配置添加到缓存 + if (ObjectUtil.isNotEmpty(entityList)) { + entityList.forEach(sysConfig -> ConfigContext.me().putConfig(sysConfig.getStr("code"), sysConfig.getStr("value"))); + } + } catch (ClassNotFoundException e) { + log.error(">>> 初始化系统配置表失败,找不到com.mysql.cj.jdbc.Driver类", e); + throw new ConfigException(ConfigExceptionEnum.CLASS_NOT_FOUND_ERROR); + } catch (SQLException sqlException) { + log.error(">>> 初始化系统配置表失败,执行查询语句失败", sqlException); + throw new ConfigException(ConfigExceptionEnum.CONFIG_SQL_EXE_ERROR); + } finally { + DbUtil.close(conn); + } + + } +} diff --git a/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/mapper/SysConfigMapper.java b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/mapper/SysConfigMapper.java new file mode 100644 index 000000000..711882a04 --- /dev/null +++ b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/mapper/SysConfigMapper.java @@ -0,0 +1,15 @@ +package cn.stylefeng.roses.kernel.config.modular.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.stylefeng.roses.kernel.config.modular.entity.SysConfig; + +/** + * 系统参数配置 Mapper 接口 + * + * @author stylefeng + * @date 2019/6/20 13:44 + */ +public interface SysConfigMapper extends BaseMapper { + + +} diff --git a/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/mapper/mapping/SysConfigMapper.xml b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/mapper/mapping/SysConfigMapper.xml new file mode 100644 index 000000000..46bfc9721 --- /dev/null +++ b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/mapper/mapping/SysConfigMapper.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/param/SysConfigParam.java b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/param/SysConfigParam.java new file mode 100644 index 000000000..2af564faf --- /dev/null +++ b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/param/SysConfigParam.java @@ -0,0 +1,86 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.config.modular.param; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.validator.validators.flag.FlagValue; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 系统参数配置参数 + * + * @author fengshuonan + * @date 2020/4/14 10:18 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysConfigParam extends BaseRequest { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class}) + private Long id; + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空,请检查name参数", groups = {add.class, edit.class}) + private String name; + + /** + * 编码 + */ + @NotBlank(message = "编码不能为空,请检查code参数", groups = {add.class, edit.class}) + private String code; + + /** + * 值 + */ + @NotBlank(message = "值不能为空,请检查value参数", groups = {add.class, edit.class}) + private String value; + + /** + * 是否是系统参数(Y-是,N-否) + */ + @NotBlank(message = "是否是系统参数不能为空,请检查sysFlag参数", groups = {add.class, edit.class}) + @FlagValue(message = "是否是系统参数格式错误,正确格式应该Y或者N,请检查sysFlag参数", groups = {add.class, edit.class}) + private String sysFlag; + + /** + * 备注 + */ + private String remark; + + /** + * 常量所属分类的编码,来自于“常量的分类”字典 + */ + @NotBlank(message = "值不能为空,请检查value参数", groups = {add.class, edit.class}) + private String groupCode; +} diff --git a/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/service/SysConfigService.java b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/service/SysConfigService.java new file mode 100644 index 000000000..767a99855 --- /dev/null +++ b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/service/SysConfigService.java @@ -0,0 +1,99 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.config.modular.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.config.modular.entity.SysConfig; +import cn.stylefeng.roses.kernel.config.modular.param.SysConfigParam; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; + +import java.util.List; + +/** + * 系统参数配置service接口 + * + * @author fengshuonan + * @date 2020/4/14 11:14 + */ +public interface SysConfigService extends IService { + + /** + * 查询系统参数配置 + * + * @param sysConfigParam 查询参数 + * @return 查询分页结果 + * @author fengshuonan + * @date 2020/4/14 11:14 + */ + PageResult page(SysConfigParam sysConfigParam); + + /** + * 查询系统参数配置 + * + * @param sysConfigParam 查询参数 + * @return 系统参数配置列表 + * @author fengshuonan + * @date 2020/4/14 11:14 + */ + List list(SysConfigParam sysConfigParam); + + /** + * 查看系统参数配置 + * + * @param sysConfigParam 查看参数 + * @return 系统参数配置 + * @author fengshuonan + * @date 2020/4/14 11:15 + */ + SysConfig detail(SysConfigParam sysConfigParam); + + /** + * 添加系统参数配置 + * + * @param sysConfigParam 添加参数 + * @author fengshuonan + * @date 2020/4/14 11:14 + */ + void add(SysConfigParam sysConfigParam); + + /** + * 删除系统参数配置 + * + * @param sysConfigParam 删除参数 + * @author fengshuonan + * @date 2020/4/14 11:15 + */ + void delete(SysConfigParam sysConfigParam); + + /** + * 编辑系统参数配置 + * + * @param sysConfigParam 编辑参数 + * @author fengshuonan + * @date 2020/4/14 11:15 + */ + void edit(SysConfigParam sysConfigParam); + +} diff --git a/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/service/impl/SysConfigServiceImpl.java b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/service/impl/SysConfigServiceImpl.java new file mode 100644 index 000000000..a61f83bb0 --- /dev/null +++ b/kernel-d-config/config-business/src/main/java/cn/stylefeng/roses/kernel/config/modular/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,157 @@ +package cn.stylefeng.roses.kernel.config.modular.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.config.modular.mapper.SysConfigMapper; +import cn.stylefeng.roses.kernel.config.modular.service.SysConfigService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.config.api.context.ConfigContext; +import cn.stylefeng.roses.kernel.config.api.exception.ConfigException; +import cn.stylefeng.roses.kernel.config.api.exception.enums.ConfigExceptionEnum; +import cn.stylefeng.roses.kernel.config.modular.entity.SysConfig; +import cn.stylefeng.roses.kernel.config.modular.param.SysConfigParam; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.rule.enums.StatusEnum; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static cn.stylefeng.roses.kernel.config.api.exception.enums.ConfigExceptionEnum.CONFIG_SYS_CAN_NOT_DELETE; + + +/** + * 系统参数配置service接口实现类 + * + * @author fengshuonan + * @date 2020/4/14 11:16 + */ +@Service +public class SysConfigServiceImpl extends ServiceImpl implements SysConfigService { + + @Override + public PageResult page(SysConfigParam sysConfigParam) { + LambdaQueryWrapper wrapper = createWrapper(sysConfigParam); + Page page = this.page(PageFactory.defaultPage(), wrapper); + return PageResultFactory.createPageResult(page); + } + + @Override + public List list(SysConfigParam sysConfigParam) { + LambdaQueryWrapper wrapper = createWrapper(sysConfigParam); + return this.list(wrapper); + } + + @Override + public SysConfig detail(SysConfigParam sysConfigParam) { + return this.querySysConfig(sysConfigParam); + } + + @Override + public void add(SysConfigParam sysConfigParam) { + + // 1.构造实体 + SysConfig sysConfig = new SysConfig(); + BeanUtil.copyProperties(sysConfigParam, sysConfig); + sysConfig.setStatus(StatusEnum.ENABLE.getCode()); + + // 2.保存到库中 + this.save(sysConfig); + + // 3.添加对应context + ConfigContext.me().putConfig(sysConfigParam.getCode(), sysConfigParam.getValue()); + } + + @Override + public void delete(SysConfigParam sysConfigParam) { + + // 1.根据id获取常量 + SysConfig sysConfig = this.querySysConfig(sysConfigParam); + + // 2.不能删除系统参数 + if (YesOrNotEnum.Y.getCode().equals(sysConfig.getSysFlag())) { + throw new ConfigException(CONFIG_SYS_CAN_NOT_DELETE); + } + + // 3.设置状态为已删除 + sysConfig.setStatus(StatusEnum.DISABLE.getCode()); + this.updateById(sysConfig); + + // 4.删除对应context + ConfigContext.me().deleteConfig(sysConfigParam.getCode()); + } + + @Override + public void edit(SysConfigParam sysConfigParam) { + + // 1.根据id获取常量信息 + SysConfig sysConfig = this.querySysConfig(sysConfigParam); + + // 2.请求参数转化为实体 + BeanUtil.copyProperties(sysConfigParam, sysConfig); + // 不能修改状态,用修改状态接口修改状态 + sysConfig.setStatus(null); + + // 3.更新记录 + this.updateById(sysConfig); + + // 4.更新对应常量context + ConfigContext.me().putConfig(sysConfigParam.getCode(), sysConfigParam.getValue()); + } + + /** + * 获取系统参数配置 + * + * @author fengshuonan + * @date 2020/4/14 11:19 + */ + private SysConfig querySysConfig(SysConfigParam sysConfigParam) { + SysConfig sysConfig = this.getById(sysConfigParam.getId()); + if (ObjectUtil.isEmpty(sysConfig)) { + String userTip = StrUtil.format(ConfigExceptionEnum.CONFIG_NOT_EXIST.getUserTip(), "id: " + sysConfigParam.getId()); + throw new ConfigException(ConfigExceptionEnum.CONFIG_NOT_EXIST, userTip); + } + return sysConfig; + } + + /** + * 创建wrapper + * + * @author fengshuonan + * @date 2020/11/6 10:16 + */ + private LambdaQueryWrapper createWrapper(SysConfigParam sysConfigParam) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + if (ObjectUtil.isNotNull(sysConfigParam)) { + // 如果名称不为空,则带上名称搜素搜条件 + if (ObjectUtil.isNotEmpty(sysConfigParam.getName())) { + queryWrapper.like(SysConfig::getName, sysConfigParam.getName()); + } + + // 如果常量编码不为空,则带上常量编码搜素搜条件 + if (ObjectUtil.isNotEmpty(sysConfigParam.getCode())) { + queryWrapper.like(SysConfig::getCode, sysConfigParam.getCode()); + } + + // 如果分类编码不为空,则带上分类编码 + if (ObjectUtil.isNotEmpty(sysConfigParam.getGroupCode())) { + queryWrapper.eq(SysConfig::getGroupCode, sysConfigParam.getGroupCode()); + } + } + + // 查询未删除的 + queryWrapper.ne(SysConfig::getDelFlag, YesOrNotEnum.Y.getCode()); + + // 按类型升序排列,同类型的排在一起 + queryWrapper.orderByDesc(SysConfig::getGroupCode); + + return queryWrapper; + } + +} diff --git a/kernel-d-config/config-sdk-db/README.md b/kernel-d-config/config-sdk-db/README.md new file mode 100644 index 000000000..c2a5506bc --- /dev/null +++ b/kernel-d-config/config-sdk-db/README.md @@ -0,0 +1 @@ +系统配置表的实现,基于数据库存储 \ No newline at end of file diff --git a/kernel-d-config/config-sdk-db/pom.xml b/kernel-d-config/config-sdk-db/pom.xml new file mode 100644 index 000000000..de3c87e5b --- /dev/null +++ b/kernel-d-config/config-sdk-db/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-config + 1.0.0 + ../pom.xml + + + config-sdk-db + + jar + + + + + + cn.stylefeng.roses + config-api + 1.0.0 + + + + + diff --git a/kernel-d-config/config-sdk-db/src/main/java/cn/stylefeng/roses/kernel/config/ConfigContainer.java b/kernel-d-config/config-sdk-db/src/main/java/cn/stylefeng/roses/kernel/config/ConfigContainer.java new file mode 100644 index 000000000..99c23ca05 --- /dev/null +++ b/kernel-d-config/config-sdk-db/src/main/java/cn/stylefeng/roses/kernel/config/ConfigContainer.java @@ -0,0 +1,105 @@ +package cn.stylefeng.roses.kernel.config; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.config.api.ConfigApi; +import cn.stylefeng.roses.kernel.config.api.exception.ConfigException; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.Set; + +import static cn.stylefeng.roses.kernel.config.api.exception.enums.ConfigExceptionEnum.CONFIG_NOT_EXIST; +import static cn.stylefeng.roses.kernel.config.api.exception.enums.ConfigExceptionEnum.CONVERT_ERROR; + +/** + * 系统配置表的实现类 + * + * @author fengshuonan + * @date 2020/10/17 14:56 + */ +@Slf4j +public class ConfigContainer implements ConfigApi { + + /** + * 系统配置的Map缓存 + */ + private static final Dict CONFIG_CONTAINER = Dict.create(); + + @Override + public void initConfig(Map configs) { + if (configs == null || configs.size() == 0) { + return; + } + CONFIG_CONTAINER.putAll(configs); + } + + @Override + public Map getAllConfigs() { + return CONFIG_CONTAINER; + } + + @Override + public Set getAllConfigKeys() { + return CONFIG_CONTAINER.keySet(); + } + + @Override + public void putConfig(String key, Object value) { + CONFIG_CONTAINER.put(key, value); + } + + @Override + public void deleteConfig(String key) { + CONFIG_CONTAINER.remove(key); + } + + @Override + public T getConfigValue(String configCode, Class clazz) throws ConfigException { + String configValue = CONFIG_CONTAINER.getStr(configCode); + if (ObjectUtil.isEmpty(configValue)) { + String format = StrUtil.format(CONFIG_NOT_EXIST.getUserTip(), configCode); + log.error(format); + throw new ConfigException(CONFIG_NOT_EXIST.getErrorCode(), format); + } else { + try { + return Convert.convert(clazz, configValue); + } catch (Exception e) { + String format = StrUtil.format(CONVERT_ERROR.getUserTip(), configCode, configValue, clazz.toString()); + log.error(format); + throw new ConfigException(CONVERT_ERROR.getErrorCode(), format); + } + } + } + + @Override + public T getConfigValueNullable(String configCode, Class clazz) { + String configValue = CONFIG_CONTAINER.getStr(configCode); + if (ObjectUtil.isEmpty(configValue)) { + String format = StrUtil.format(CONFIG_NOT_EXIST.getUserTip(), configCode); + log.error(format); + return null; + } else { + try { + return Convert.convert(clazz, configValue); + } catch (Exception e) { + String format = StrUtil.format(CONVERT_ERROR.getUserTip(), configCode, configValue, clazz.toString()); + log.error(format); + return null; + } + } + } + + @Override + public T getSysConfigValueWithDefault(String configCode, Class clazz, T defaultValue) { + T value = this.getConfigValueNullable(configCode, clazz); + if (value == null) { + return defaultValue; + } else { + return value; + } + } + +} diff --git a/kernel-d-config/config-spring-boot-starter/README.md b/kernel-d-config/config-spring-boot-starter/README.md new file mode 100644 index 000000000..778711c08 --- /dev/null +++ b/kernel-d-config/config-spring-boot-starter/README.md @@ -0,0 +1 @@ +配置模块的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-config/config-spring-boot-starter/pom.xml b/kernel-d-config/config-spring-boot-starter/pom.xml new file mode 100644 index 000000000..f85c52fa6 --- /dev/null +++ b/kernel-d-config/config-spring-boot-starter/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-config + 1.0.0 + ../pom.xml + + + config-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + config-business + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-config/config-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/config/starter/GunsSysConfigAutoConfiguration.java b/kernel-d-config/config-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/config/starter/GunsSysConfigAutoConfiguration.java new file mode 100644 index 000000000..d980a6840 --- /dev/null +++ b/kernel-d-config/config-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/config/starter/GunsSysConfigAutoConfiguration.java @@ -0,0 +1,15 @@ +package cn.stylefeng.roses.kernel.config.starter; + +import org.springframework.context.annotation.Configuration; + +/** + * 系统配置模块的自动配置类 + * + * @author fengshuonan + * @date 2020/11/30 22:24 + */ +@Configuration +public class GunsSysConfigAutoConfiguration { + + +} diff --git a/kernel-d-config/config-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-config/config-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..c5d42e7da --- /dev/null +++ b/kernel-d-config/config-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.config.starter.GunsSysConfigAutoConfiguration +org.springframework.context.ApplicationListener=\ + cn.stylefeng.roses.kernel.config.modular.listener.ConfigInitListener \ No newline at end of file diff --git a/kernel-d-config/pom.xml b/kernel-d-config/pom.xml new file mode 100644 index 000000000..d422b58a8 --- /dev/null +++ b/kernel-d-config/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-config + + pom + + + config-api + config-sdk-db + config-business + config-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-db/README.md b/kernel-d-db/README.md new file mode 100644 index 000000000..6543a99ce --- /dev/null +++ b/kernel-d-db/README.md @@ -0,0 +1,3 @@ +跟数据库交互的框架 + +公司约定用mybatis-plus为核心框架,如无特殊情况,不允许切换dao框架 \ No newline at end of file diff --git a/kernel-d-db/db-api/README.md b/kernel-d-db/db-api/README.md new file mode 100644 index 000000000..d27e3f76f --- /dev/null +++ b/kernel-d-db/db-api/README.md @@ -0,0 +1 @@ +数据库api模块 \ No newline at end of file diff --git a/kernel-d-db/db-api/pom.xml b/kernel-d-db/db-api/pom.xml new file mode 100644 index 000000000..5a709df68 --- /dev/null +++ b/kernel-d-db/db-api/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-db + 1.0.0 + ../pom.xml + + + db-api + + jar + + + + + + + com.baomidou + mybatis-plus-boot-starter + + + + + + javax.servlet + javax.servlet-api + + + + + + com.alibaba + druid + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/DbOperatorApi.java b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/DbOperatorApi.java new file mode 100644 index 000000000..6360a60d0 --- /dev/null +++ b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/DbOperatorApi.java @@ -0,0 +1,43 @@ +package cn.stylefeng.roses.kernel.db.api; + +import java.util.Set; + +/** + * 数据库操作的api,用于快速进行sql操作并获取结果 + * + * @author fengshuonan + * @date 2020/11/4 14:43 + */ +public interface DbOperatorApi { + + /** + * 返回SelectCount SQL执行的结果 + * + * @param sql 带有select count()相关语句的sql + * @param args sql中的参数 + * @return sql执行的结果,取第一行第一个数字 + * @author fengshuonan + * @date 2020/11/4 14:43 + */ + int selectCount(String sql, Object... args); + + /** + * 获取某个表,某条数据的所有子列表 TODO 测试 + *

+ * 本方法用在带有层级关系的表,并且有 "pids" 类似的字段 + *

+ * 通过 like 操作可以查询到该条数据的所有子数据 + *

+ * pids的组成规范必须是[0],[xxx],[xxx] + * + * @param tableName 表名称,例如sys_user + * @param parentIdsFieldName 父级ids的字段名 + * @param keyFieldName 主键id的字段名 + * @param keyFieldValue 主键id的值 + * @return keyFieldValue的值 + * @author fengshuonan + * @date 2020/11/5 17:32 + */ + Set findSubListByParentId(String tableName, String parentIdsFieldName, String keyFieldName, Long keyFieldValue); + +} diff --git a/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/constants/DbConstants.java b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/constants/DbConstants.java new file mode 100644 index 000000000..9d92cb0ac --- /dev/null +++ b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/constants/DbConstants.java @@ -0,0 +1,21 @@ +package cn.stylefeng.roses.kernel.db.api.constants; + +/** + * 数据库模块的常量 + * + * @author fengshuonan + * @date 2020/10/16 11:05 + */ +public interface DbConstants { + + /** + * db模块的名称 + */ + String DB_MODULE_NAME = "kernel-d-db"; + + /** + * 异常枚举的步进值 + */ + String DB_EXCEPTION_STEP_CODE = "02"; + +} diff --git a/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/constants/DbFieldConstants.java b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/constants/DbFieldConstants.java new file mode 100644 index 000000000..055bd7e46 --- /dev/null +++ b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/constants/DbFieldConstants.java @@ -0,0 +1,41 @@ +package cn.stylefeng.roses.kernel.db.api.constants; + +/** + * 数据库常用字段的枚举 + * + * @author fengshuonan + * @date 2020/10/16 17:07 + */ +public interface DbFieldConstants { + + /** + * 主键id的字段名 + */ + String ID = "id"; + + /** + * 创建用户的字段名 + */ + String CREATE_USER = "createUser"; + + /** + * 创建时间的字段名 + */ + String CREATE_TIME = "createTime"; + + /** + * 更新用户的字段名 + */ + String UPDATE_USER = "updateUser"; + + /** + * 更新时间的字段名 + */ + String UPDATE_TIME = "updateTime"; + + /** + * 删除标记的字段名 + */ + String DEL_FLAG = "delFlag"; + +} diff --git a/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/context/DbOperatorContext.java b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/context/DbOperatorContext.java new file mode 100644 index 000000000..1586b08ec --- /dev/null +++ b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/context/DbOperatorContext.java @@ -0,0 +1,18 @@ +package cn.stylefeng.roses.kernel.db.api.context; + +import cn.hutool.extra.spring.SpringUtil; +import cn.stylefeng.roses.kernel.db.api.DbOperatorApi; + +/** + * 获取sql操作器 + * + * @author fengshuonan + * @date 2020/11/4 15:07 + */ +public class DbOperatorContext { + + public static DbOperatorApi me() { + return SpringUtil.getBean(DbOperatorApi.class); + } + +} diff --git a/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/enums/DbTypeEnum.java b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/enums/DbTypeEnum.java new file mode 100644 index 000000000..297611cd4 --- /dev/null +++ b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/enums/DbTypeEnum.java @@ -0,0 +1,51 @@ +package cn.stylefeng.roses.kernel.db.api.enums; + +import lombok.Getter; + +/** + * 不同数据库类型的枚举 + *

+ * 用于标识mapping.xml中不同数据库的标识 + * + * @author fengshuonan + * @date 2020/6/20 21:08 + */ +@Getter +public enum DbTypeEnum { + + /** + * mysql + */ + MYSQL("mysql", "select 1"), + + /** + * pgsql + */ + PG_SQL("postgresql", "select version()"), + + /** + * oracle + */ + ORACLE("oracle", "select 1 from dual"), + + /** + * mssql + */ + MS_SQL("sqlserver", "select 1"); + + /** + * 数据库编码,也是datasource url上会有的关键字 + */ + private final String code; + + /** + * 检测数据库是否运行的sql语句 + */ + private final String validateQuery; + + DbTypeEnum(String code, String validateQuery) { + this.code = code; + this.validateQuery = validateQuery; + } + +} diff --git a/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/exception/DaoException.java b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/exception/DaoException.java new file mode 100644 index 000000000..3e23f7fc3 --- /dev/null +++ b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/exception/DaoException.java @@ -0,0 +1,19 @@ +package cn.stylefeng.roses.kernel.db.api.exception; + +import cn.stylefeng.roses.kernel.db.api.constants.DbConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; + +/** + * 数据库操作异常 + * + * @author fengshuonan + * @date 2020/10/15 15:59 + */ +public class DaoException extends ServiceException { + + public DaoException(AbstractExceptionEnum exception) { + super(DbConstants.DB_MODULE_NAME, exception); + } + +} diff --git a/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/exception/enums/DaoExceptionEnum.java b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/exception/enums/DaoExceptionEnum.java new file mode 100644 index 000000000..0ffc91180 --- /dev/null +++ b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/exception/enums/DaoExceptionEnum.java @@ -0,0 +1,37 @@ +package cn.stylefeng.roses.kernel.db.api.exception.enums; + +import cn.stylefeng.roses.kernel.db.api.constants.DbConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import lombok.Getter; + +/** + * 数据库相关操作的异常枚举 + * + * @author fengshuonan + * @date 2020/10/16 10:53 + */ +@Getter +public enum DaoExceptionEnum implements AbstractExceptionEnum { + + /** + * 数据库操作未知异常 + */ + DAO_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DbConstants.DB_EXCEPTION_STEP_CODE + "01", "数据库操作未知异常"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + DaoExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/factory/DruidFactory.java b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/factory/DruidFactory.java new file mode 100644 index 000000000..5b87d5da1 --- /dev/null +++ b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/factory/DruidFactory.java @@ -0,0 +1,109 @@ +package cn.stylefeng.roses.kernel.db.api.factory; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.druid.pool.DruidDataSource; +import cn.stylefeng.roses.kernel.db.api.enums.DbTypeEnum; +import cn.stylefeng.roses.kernel.db.api.pojo.druid.DruidProperties; +import lombok.extern.slf4j.Slf4j; + +import java.sql.SQLException; + +/** + * druid连接池创建工厂 + * + * @author fengshuonan + * @date 2020/10/16 15:59 + */ +@Slf4j +public class DruidFactory { + + /** + * 创建druid连接池 + * + * @author fengshuonan + * @date 2020/10/16 16:00 + */ + public static DruidDataSource createDruidDataSource(DruidProperties druidProperties) { + DruidDataSource dataSource = new DruidDataSource(); + + // 数据库连接相关设置 + dataSource.setUrl(druidProperties.getUrl()); + dataSource.setUsername(druidProperties.getUsername()); + dataSource.setPassword(druidProperties.getPassword()); + + // 驱动 + if (StrUtil.isNotBlank(druidProperties.getDriverClassName())) { + dataSource.setDriverClassName(druidProperties.getDriverClassName()); + } + + // 定义初始连接数 + dataSource.setInitialSize(druidProperties.getInitialSize()); + + // 定义最大连接数 + dataSource.setMaxActive(druidProperties.getMaxActive()); + + // 最小空闲 + dataSource.setMinIdle(druidProperties.getMinIdle()); + + // 最长等待时间 + dataSource.setMaxWait(druidProperties.getMaxWait()); + + // 是否缓存preparedStatement + dataSource.setPoolPreparedStatements(druidProperties.getPoolPreparedStatements()); + + // PSCache数量 + dataSource.setMaxPoolPreparedStatementPerConnectionSize(druidProperties.getMaxPoolPreparedStatementPerConnectionSize()); + + // 检测连接是否有效的sql + if (StrUtil.isEmpty(druidProperties.getUrl())) { + dataSource.setValidationQuery(getValidateQueryByUrl(druidProperties.getUrl())); + } + + // 检测连接是否有效的超时时间 + dataSource.setValidationQueryTimeout(druidProperties.getValidationQueryTimeout()); + + // 连接获取时候的检测 + dataSource.setTestOnBorrow(druidProperties.getTestOnBorrow()); + dataSource.setTestOnReturn(druidProperties.getTestOnReturn()); + dataSource.setTestWhileIdle(druidProperties.getTestWhileIdle()); + + // 连接池中的minIdle数量以内的连接 + dataSource.setKeepAlive(druidProperties.getKeepAlive()); + + // 检测的间隔时间 + dataSource.setTimeBetweenEvictionRunsMillis(druidProperties.getTimeBetweenEvictionRunsMillis()); + + // 保持空闲的连接多久以后会被清除 + dataSource.setMinEvictableIdleTimeMillis(druidProperties.getMinEvictableIdleTimeMillis()); + + try { + dataSource.setFilters(druidProperties.getFilters()); + } catch (SQLException e) { + log.error(">>> 数据库连接池初始化异常:{}", e.getMessage()); + } + + return dataSource; + } + + /** + * 根据数据库url获取validate query + * + * @param url 数据库配置的url + * @author fengshuonan + * @date 2020/10/16 16:12 + */ + private static String getValidateQueryByUrl(String url) { + if (url.contains(DbTypeEnum.ORACLE.getCode())) { + return DbTypeEnum.ORACLE.getValidateQuery(); + } + if (url.contains(DbTypeEnum.MS_SQL.getCode())) { + return DbTypeEnum.MS_SQL.getValidateQuery(); + } + if (url.contains(DbTypeEnum.PG_SQL.getCode())) { + return DbTypeEnum.PG_SQL.getValidateQuery(); + } + + return DbTypeEnum.MYSQL.getValidateQuery(); + } + +} diff --git a/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/factory/PageFactory.java b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/factory/PageFactory.java new file mode 100644 index 000000000..af903bcd1 --- /dev/null +++ b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/factory/PageFactory.java @@ -0,0 +1,55 @@ +package cn.stylefeng.roses.kernel.db.api.factory; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import cn.stylefeng.roses.kernel.rule.util.HttpServletUtil; + +import javax.servlet.http.HttpServletRequest; + +/** + * 分页参数快速获取 + * + * @author fengshuonan + * @date 2020/10/17 17:33 + */ +public class PageFactory { + + /** + * 每页大小(默认20) + */ + private static final String PAGE_SIZE_PARAM_NAME = "pageSize"; + + /** + * 第几页(从1开始) + */ + private static final String PAGE_NO_PARAM_NAME = "pageNo"; + + /** + * 默认分页,在使用时PageFactory.defaultPage会自动获取pageSize和pageNo参数 + * + * @author fengshuonan + * @date 2020/3/30 16:42 + */ + public static Page defaultPage() { + + int pageSize = 20; + int pageNo = 1; + + HttpServletRequest request = HttpServletUtil.getRequest(); + + //每页条数 + String pageSizeString = request.getParameter(PAGE_SIZE_PARAM_NAME); + if (ObjectUtil.isNotEmpty(pageSizeString)) { + pageSize = Integer.parseInt(pageSizeString); + } + + //第几页 + String pageNoString = request.getParameter(PAGE_NO_PARAM_NAME); + if (ObjectUtil.isNotEmpty(pageNoString)) { + pageNo = Integer.parseInt(pageNoString); + } + + return new Page<>(pageNo, pageSize); + } + +} \ No newline at end of file diff --git a/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/factory/PageResultFactory.java b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/factory/PageResultFactory.java new file mode 100644 index 000000000..32c5ddb49 --- /dev/null +++ b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/factory/PageResultFactory.java @@ -0,0 +1,35 @@ +package cn.stylefeng.roses.kernel.db.api.factory; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.PageUtil; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +/** + * 分页的返回结果创建工厂 + *

+ * 一般由mybatis-plus的Page对象转为PageResult + * + * @author fengshuonan + * @date 2020/10/15 16:57 + */ +public class PageResultFactory { + + /** + * 将mybatis-plus的page转成自定义的PageResult,扩展了totalPage总页数 + * + * @author fengshuonan + * @date 2020/10/15 15:53 + */ + public static PageResult createPageResult(Page page) { + PageResult pageResult = new PageResult<>(); + pageResult.setRows(page.getRecords()); + pageResult.setTotalRows(Convert.toInt(page.getTotal())); + pageResult.setPageNo(Convert.toInt(page.getCurrent())); + pageResult.setPageSize(Convert.toInt(page.getSize())); + pageResult.setTotalPage( + PageUtil.totalPage(Convert.toInt(page.getTotal()), Convert.toInt(page.getSize()))); + return pageResult; + } + +} diff --git a/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/pojo/druid/DruidProperties.java b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/pojo/druid/DruidProperties.java new file mode 100644 index 000000000..cdc9eebcf --- /dev/null +++ b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/pojo/druid/DruidProperties.java @@ -0,0 +1,130 @@ +package cn.stylefeng.roses.kernel.db.api.pojo.druid; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +/** + *

数据库数据源配置

+ *

说明:类中属性包含默认值的不要在这里修改,应该在"application.yml"中配置

+ * + * @author stylefeng + * @date 2017/5/21 11:18 + */ +@Data +@Slf4j +public class DruidProperties { + + /** + * 数据源名称,非druid的官方配置 + */ + private String dataSourceName; + + /** + * 连接数据库的url,不同数据库不一样。 + * 例如: + * mysql : jdbc:mysql://10.20.153.104:3306/druid2 + * oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto + */ + private String url; + + /** + * 连接数据库的用户名 + */ + private String username; + + /** + * 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。 + * 详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter + */ + private String password; + + /** + * 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName + */ + private String driverClassName; + + /** + * 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 + */ + private Integer initialSize = 2; + + /** + * 最大连接池数量 + */ + private Integer maxActive = 20; + + /** + * 最小连接池数量 + */ + private Integer minIdle = 1; + + /** + * 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 + */ + private Integer maxWait = 60000; + + /** + * 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 + */ + private Boolean poolPreparedStatements = true; + + /** + * 要启用PSCache,必须配置大于0,可以配置-1关闭 + * 当大于0时,poolPreparedStatements自动触发修改为true。 + * 在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 + */ + private Integer maxPoolPreparedStatementPerConnectionSize = 100; + + /** + * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。 + * 如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 + */ + private String validationQuery; + + /** + * 单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法 + */ + private Integer validationQueryTimeout = 10; + + /** + * 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 + */ + private Boolean testOnBorrow = true; + + /** + * 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 + */ + private Boolean testOnReturn = true; + + /** + * 建议配置为true,不影响性能,并且保证安全性。 + * 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 + */ + private Boolean testWhileIdle = true; + + /** + * 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。 + */ + private Boolean keepAlive = false; + + /** + * 有两个含义: + * 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于 minEvictableIdleTimeMillis 则关闭物理连接。 + * 2) testWhileIdle 的判断依据,详细看 testWhileIdle 属性的说明 + */ + private Integer timeBetweenEvictionRunsMillis = 60000; + + /** + * 连接保持空闲而不被驱逐的最小时间 + */ + private Integer minEvictableIdleTimeMillis = 300000; + + /** + * 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: + * 监控统计用的filter:stat + * 日志用的filter:log4j + * 防御sql注入的filter:wall + */ + private String filters = "stat,wall"; + +} diff --git a/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/pojo/entity/BaseEntity.java b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/pojo/entity/BaseEntity.java new file mode 100644 index 000000000..2371b0ef6 --- /dev/null +++ b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/pojo/entity/BaseEntity.java @@ -0,0 +1,49 @@ +package cn.stylefeng.roses.kernel.db.api.pojo.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 实体的基础类 + *

+ * 实体是跟数据库有联系的,所有包含了@TableField注解 + *

+ * 公司dao框架规定使用mybatis-plus + * + * @author fengshuonan + * @date 2020/10/14 18:08 + */ +@Data +public class BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 创建人 + */ + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.UPDATE) + private Date updateTime; + + /** + * 更新人 + */ + @TableField(fill = FieldFill.UPDATE) + private Long updateUser; + +} diff --git a/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/pojo/page/PageResult.java b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/pojo/page/PageResult.java new file mode 100644 index 000000000..171166f10 --- /dev/null +++ b/kernel-d-db/db-api/src/main/java/cn/stylefeng/roses/kernel/db/api/pojo/page/PageResult.java @@ -0,0 +1,44 @@ +package cn.stylefeng.roses.kernel.db.api.pojo.page; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 分页结果封装 + * + * @author fengshuonan + * @date 2020/10/15 15:53 + */ +@Data +public class PageResult implements Serializable { + + private static final long serialVersionUID = -1L; + + /** + * 第几页 + */ + private Integer pageNo = 1; + + /** + * 每页条数 + */ + private Integer pageSize = 20; + + /** + * 总页数 + */ + private Integer totalPage = 0; + + /** + * 总记录数 + */ + private Integer totalRows = 0; + + /** + * 结果集 + */ + private List rows; + +} diff --git a/kernel-d-db/db-sdk-mp/README.md b/kernel-d-db/db-sdk-mp/README.md new file mode 100644 index 000000000..a65cb0cb2 --- /dev/null +++ b/kernel-d-db/db-sdk-mp/README.md @@ -0,0 +1 @@ +数据库操作,mybatis-plus相关的工具和配置 \ No newline at end of file diff --git a/kernel-d-db/db-sdk-mp/pom.xml b/kernel-d-db/db-sdk-mp/pom.xml new file mode 100644 index 000000000..44c604fd0 --- /dev/null +++ b/kernel-d-db/db-sdk-mp/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-db + 1.0.0 + ../pom.xml + + + db-sdk-mp + + jar + + + + + + cn.stylefeng.roses + db-api + 1.0.0 + + + + + + cn.stylefeng.roses + auth-api + 1.0.0 + + + + + diff --git a/kernel-d-db/db-sdk-mp/src/main/java/cn/stylefeng/roses/kernel/db/mp/dbid/CustomDatabaseIdProvider.java b/kernel-d-db/db-sdk-mp/src/main/java/cn/stylefeng/roses/kernel/db/mp/dbid/CustomDatabaseIdProvider.java new file mode 100644 index 000000000..a40781864 --- /dev/null +++ b/kernel-d-db/db-sdk-mp/src/main/java/cn/stylefeng/roses/kernel/db/mp/dbid/CustomDatabaseIdProvider.java @@ -0,0 +1,37 @@ +package cn.stylefeng.roses.kernel.db.mp.dbid; + +import cn.stylefeng.roses.kernel.db.api.enums.DbTypeEnum; +import org.apache.ibatis.mapping.DatabaseIdProvider; + +import javax.sql.DataSource; +import java.sql.SQLException; + +/** + * 数据库id选择器,用在一个mapper.xml中包含多种数据库的sql时候 + *

+ * 提供给mybatis能识别不同数据库的标识 + * + * @author fengshuonan + * @date 2020/10/16 17:02 + */ +public class CustomDatabaseIdProvider implements DatabaseIdProvider { + + @Override + public String getDatabaseId(DataSource dataSource) throws SQLException { + + String url = dataSource.getConnection().getMetaData().getURL(); + + if (url.contains(DbTypeEnum.ORACLE.getCode())) { + return DbTypeEnum.ORACLE.getCode(); + } + if (url.contains(DbTypeEnum.MS_SQL.getCode())) { + return DbTypeEnum.MS_SQL.getCode(); + } + if (url.contains(DbTypeEnum.PG_SQL.getCode())) { + return DbTypeEnum.PG_SQL.getCode(); + } + + return DbTypeEnum.MYSQL.getCode(); + } + +} diff --git a/kernel-d-db/db-sdk-mp/src/main/java/cn/stylefeng/roses/kernel/db/mp/dboperator/DbOperatorImpl.java b/kernel-d-db/db-sdk-mp/src/main/java/cn/stylefeng/roses/kernel/db/mp/dboperator/DbOperatorImpl.java new file mode 100644 index 000000000..96c8656eb --- /dev/null +++ b/kernel-d-db/db-sdk-mp/src/main/java/cn/stylefeng/roses/kernel/db/mp/dboperator/DbOperatorImpl.java @@ -0,0 +1,38 @@ +package cn.stylefeng.roses.kernel.db.mp.dboperator; + +import com.baomidou.mybatisplus.extension.toolkit.SqlRunner; +import cn.stylefeng.roses.kernel.db.api.DbOperatorApi; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 数据库操作的实现 + * + * @author fengshuonan + * @date 2020/11/4 14:48 + */ +@Service +public class DbOperatorImpl implements DbOperatorApi { + + @Override + public int selectCount(String sql, Object... args) { + return SqlRunner.db().selectCount(sql, args); + } + + @Override + public Set findSubListByParentId(String tableName, String parentIdsFieldName, String keyFieldName, Long keyFieldValue) { + + // 组装sql + String sql = "select {0} from {1} where {2} like '%[{3}]%'"; + + // 查询所有子级的id集合,结果不包含被查询的keyFieldValue + List subIds = SqlRunner.db().selectObjs(sql, keyFieldName, tableName, parentIdsFieldName, keyFieldValue.toString()); + + // 转为Set + return subIds.stream().map(i -> Long.valueOf(i.toString())).collect(Collectors.toSet()); + } + +} diff --git a/kernel-d-db/db-sdk-mp/src/main/java/cn/stylefeng/roses/kernel/db/mp/fieldfill/CustomMetaObjectHandler.java b/kernel-d-db/db-sdk-mp/src/main/java/cn/stylefeng/roses/kernel/db/mp/fieldfill/CustomMetaObjectHandler.java new file mode 100644 index 000000000..e26e3b076 --- /dev/null +++ b/kernel-d-db/db-sdk-mp/src/main/java/cn/stylefeng/roses/kernel/db/mp/fieldfill/CustomMetaObjectHandler.java @@ -0,0 +1,75 @@ +package cn.stylefeng.roses.kernel.db.mp.fieldfill; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import cn.stylefeng.roses.kernel.auth.api.context.LoginContext; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.ReflectionException; + +import java.util.Date; + +import static cn.stylefeng.roses.kernel.db.api.constants.DbFieldConstants.*; + +/** + * 字段自动填充工具,在mybatis-plus执行更新和新增操作时候,会对指定字段进行自动填充,例如 create_time 等字段 + * + * @author fengshuonan + * @date 2020/10/16 17:14 + */ +@Slf4j +public class CustomMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + + try { + + // 设置createUser(BaseEntity) + setFieldValByName(CREATE_USER, this.getUserUniqueId(), metaObject); + + // 设置createTime(BaseEntity) + setFieldValByName(CREATE_TIME, new Date(), metaObject); + + // 设置删除标记,默认N,未删除 + setFieldValByName(DEL_FLAG, YesOrNotEnum.N.getCode(), metaObject); + + + } catch (ReflectionException e) { + log.warn(">>> CustomMetaObjectHandler处理过程中无相关字段,不做处理"); + } + + } + + @Override + public void updateFill(MetaObject metaObject) { + + try { + + // 设置updateUser(BaseEntity) + setFieldValByName(UPDATE_USER, this.getUserUniqueId(), metaObject); + + // 设置updateTime(BaseEntity) + setFieldValByName(UPDATE_TIME, new Date(), metaObject); + + } catch (ReflectionException e) { + log.warn(">>> CustomMetaObjectHandler处理过程中无相关字段,不做处理"); + } + + } + + /** + * 获取用户唯一id + */ + private Long getUserUniqueId() { + + try { + return LoginContext.me().getLoginUser().getId(); + } catch (Exception e) { + //如果获取不到就返回-1 + return -1L; + } + + } + +} \ No newline at end of file diff --git a/kernel-d-db/db-spring-boot-starter/README.md b/kernel-d-db/db-spring-boot-starter/README.md new file mode 100644 index 000000000..b15a50d29 --- /dev/null +++ b/kernel-d-db/db-spring-boot-starter/README.md @@ -0,0 +1 @@ +数据库连接和dao框架的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-db/db-spring-boot-starter/pom.xml b/kernel-d-db/db-spring-boot-starter/pom.xml new file mode 100644 index 000000000..7055a839f --- /dev/null +++ b/kernel-d-db/db-spring-boot-starter/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-db + 1.0.0 + ../pom.xml + + + db-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/GunsDataSourceAutoConfiguration.java b/kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/GunsDataSourceAutoConfiguration.java new file mode 100644 index 000000000..875c29fb4 --- /dev/null +++ b/kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/GunsDataSourceAutoConfiguration.java @@ -0,0 +1,38 @@ +package cn.stylefeng.roses.kernel.db.starter; + +import com.alibaba.druid.pool.DruidDataSource; +import cn.stylefeng.roses.kernel.db.api.factory.DruidFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.druid.DruidProperties; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import javax.sql.DataSource; + +/** + * 数据库连接和DAO框架的配置 + * + * @author fengshuonan + * @date 2020/11/30 22:24 + */ +@Configuration +@Import(GunsDruidPropertiesAutoConfiguration.class) +@AutoConfigureBefore(DataSourceAutoConfiguration.class) +public class GunsDataSourceAutoConfiguration { + + /** + * druid数据库连接池 + * + * @author fengshuonan + * @date 2020/11/30 22:37 + */ + @Bean(initMethod = "init") + @ConditionalOnMissingBean(DataSource.class) + public DruidDataSource druidDataSource(DruidProperties druidProperties) { + return DruidFactory.createDruidDataSource(druidProperties); + } + +} diff --git a/kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/GunsDruidPropertiesAutoConfiguration.java b/kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/GunsDruidPropertiesAutoConfiguration.java new file mode 100644 index 000000000..f1c57f8e1 --- /dev/null +++ b/kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/GunsDruidPropertiesAutoConfiguration.java @@ -0,0 +1,34 @@ +package cn.stylefeng.roses.kernel.db.starter; + +import cn.stylefeng.roses.kernel.db.api.pojo.druid.DruidProperties; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 数据库连接和DAO框架的配置 + * + * @author fengshuonan + * @date 2020/11/30 22:24 + */ +@Configuration +@AutoConfigureBefore(DataSourceAutoConfiguration.class) +public class GunsDruidPropertiesAutoConfiguration { + + /** + * druid属性配置 + * + * @author fengshuonan + * @date 2020/11/30 22:36 + */ + @Bean + @ConfigurationProperties(prefix = "spring.datasource") + @ConditionalOnMissingBean(DruidProperties.class) + public DruidProperties druidProperties() { + return new DruidProperties(); + } + +} diff --git a/kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/GunsMyBatisPlusAutoConfiguration.java b/kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/GunsMyBatisPlusAutoConfiguration.java new file mode 100644 index 000000000..b3ed4958f --- /dev/null +++ b/kernel-d-db/db-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/db/starter/GunsMyBatisPlusAutoConfiguration.java @@ -0,0 +1,55 @@ +package cn.stylefeng.roses.kernel.db.starter; + +import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import cn.stylefeng.roses.kernel.db.mp.dbid.CustomDatabaseIdProvider; +import cn.stylefeng.roses.kernel.db.mp.fieldfill.CustomMetaObjectHandler; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * mybatis-plus的插件配置 + * + * @author fengshuonan + * @date 2020/11/30 22:40 + */ +@Configuration +@AutoConfigureAfter(MybatisPlusAutoConfiguration.class) +public class GunsMyBatisPlusAutoConfiguration { + + /** + * 分页插件 + * + * @author fengshuonan + * @date 2020/11/30 22:41 + */ + @Bean + public PaginationInnerInterceptor paginationInterceptor() { + return new PaginationInnerInterceptor(); + } + + /** + * 公共字段填充插件 + * + * @author fengshuonan + * @date 2020/11/30 22:41 + */ + @Bean + public MetaObjectHandler metaObjectHandler() { + return new CustomMetaObjectHandler(); + } + + /** + * 数据库id选择器,兼容多个数据库sql脚本 + * + * @author fengshuonan + * @date 2020/11/30 22:42 + */ + @Bean + public CustomDatabaseIdProvider customDatabaseIdProvider() { + return new CustomDatabaseIdProvider(); + } + +} diff --git a/kernel-d-db/db-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-db/db-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..4d963a0f8 --- /dev/null +++ b/kernel-d-db/db-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.db.starter.GunsDataSourceAutoConfiguration,\ + cn.stylefeng.roses.kernel.db.starter.GunsDataSourceAutoConfiguration,\ + cn.stylefeng.roses.kernel.db.starter.GunsMyBatisPlusAutoConfiguration \ No newline at end of file diff --git a/kernel-d-db/pom.xml b/kernel-d-db/pom.xml new file mode 100644 index 000000000..2a6a31890 --- /dev/null +++ b/kernel-d-db/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-db + + pom + + + db-api + db-sdk-mp + db-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-ds-container/README.md b/kernel-d-ds-container/README.md new file mode 100644 index 000000000..27b223f12 --- /dev/null +++ b/kernel-d-ds-container/README.md @@ -0,0 +1 @@ +datasource-container 数据源容器,本模块用于多数据源功能 \ No newline at end of file diff --git a/kernel-d-ds-container/ds-container-api/README.md b/kernel-d-ds-container/ds-container-api/README.md new file mode 100644 index 000000000..6d6b783b4 --- /dev/null +++ b/kernel-d-ds-container/ds-container-api/README.md @@ -0,0 +1 @@ +多数据源模块的api \ No newline at end of file diff --git a/kernel-d-ds-container/ds-container-api/pom.xml b/kernel-d-ds-container/ds-container-api/pom.xml new file mode 100644 index 000000000..1d98031ff --- /dev/null +++ b/kernel-d-ds-container/ds-container-api/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-ds-container + 1.0.0 + ../pom.xml + + + ds-container-api + + jar + + + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/annotation/DataSource.java b/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/annotation/DataSource.java new file mode 100644 index 000000000..b48cee7e9 --- /dev/null +++ b/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/annotation/DataSource.java @@ -0,0 +1,21 @@ +package cn.stylefeng.roses.kernel.dsctn.api.annotation; + +import java.lang.annotation.*; + +/** + * 多数据源标识的注解 + * + * @author fengshuonan + * @date 2020/10/31 22:50 + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface DataSource { + + /** + * 数据源的名称 + */ + String name() default ""; + +} diff --git a/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/constants/DatasourceContainerConstants.java b/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/constants/DatasourceContainerConstants.java new file mode 100644 index 000000000..f1be18ca7 --- /dev/null +++ b/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/constants/DatasourceContainerConstants.java @@ -0,0 +1,36 @@ +package cn.stylefeng.roses.kernel.dsctn.api.constants; + +/** + * 数据源容器的常量 + * + * @author fengshuonan + * @date 2020/10/31 21:58 + */ +public interface DatasourceContainerConstants { + + /** + * db模块的名称 + */ + String DS_CTN_MODULE_NAME = "kernel-d-ds-container"; + + /** + * 异常枚举的步进值 + */ + String DS_CTN_EXCEPTION_STEP_CODE = "16"; + + /** + * 主数据源名称 + */ + String MASTER_DATASOURCE_NAME = "master"; + + /** + * 多数据源切换的aop的顺序 + */ + int MULTI_DATA_SOURCE_EXCHANGE_AOP = 1; + + /** + * 租户数据源标识前缀 + */ + String TENANT_DB_PREFIX = "sys_tenant_db_"; + +} diff --git a/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/context/CurrentDataSourceContext.java b/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/context/CurrentDataSourceContext.java new file mode 100644 index 000000000..246d1dd78 --- /dev/null +++ b/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/context/CurrentDataSourceContext.java @@ -0,0 +1,43 @@ +package cn.stylefeng.roses.kernel.dsctn.api.context; + +/** + * 利用ThreadLocal缓存当前请求的数据源 + * + * @author fengshuonan + * @date 2020/10/31 22:58 + */ +public class CurrentDataSourceContext { + + private static final ThreadLocal DATASOURCE_CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * 设置数据源类型 + * + * @param dataSourceName 数据库类型 + * @date 2020/8/24 + */ + public static void setDataSourceName(String dataSourceName) { + DATASOURCE_CONTEXT_HOLDER.set(dataSourceName); + } + + /** + * 获取数据源类型 + * + * @author fengshuonan + * @date 2020/8/24 + */ + public static String getDataSourceName() { + return DATASOURCE_CONTEXT_HOLDER.get(); + } + + /** + * 清除数据源类型 + * + * @author fengshuonan + * @date 2020/8/24 + */ + public static void clearDataSourceName() { + DATASOURCE_CONTEXT_HOLDER.remove(); + } + +} diff --git a/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/exception/DatasourceContainerException.java b/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/exception/DatasourceContainerException.java new file mode 100644 index 000000000..bdcc92749 --- /dev/null +++ b/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/exception/DatasourceContainerException.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.dsctn.api.exception; + +import cn.stylefeng.roses.kernel.dsctn.api.constants.DatasourceContainerConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; + +/** + * 数据源容器操作异常 + * + * @author fengshuonan + * @date 2020/10/31 22:10 + */ +public class DatasourceContainerException extends ServiceException { + + public DatasourceContainerException(AbstractExceptionEnum exception) { + super(DatasourceContainerConstants.DS_CTN_MODULE_NAME, exception); + } + + public DatasourceContainerException(AbstractExceptionEnum exception, String userTip) { + super(DatasourceContainerConstants.DS_CTN_MODULE_NAME, exception.getErrorCode(), userTip); + } + +} diff --git a/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/exception/enums/DatasourceContainerExceptionEnum.java b/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/exception/enums/DatasourceContainerExceptionEnum.java new file mode 100644 index 000000000..9c85a8ecd --- /dev/null +++ b/kernel-d-ds-container/ds-container-api/src/main/java/cn/stylefeng/roses/kernel/dsctn/api/exception/enums/DatasourceContainerExceptionEnum.java @@ -0,0 +1,102 @@ +package cn.stylefeng.roses.kernel.dsctn.api.exception.enums; + +import cn.stylefeng.roses.kernel.dsctn.api.constants.DatasourceContainerConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import lombok.Getter; + +/** + * 数据源容器异常枚举 + * + * @author fengshuonan + * @date 2020/10/31 22:10 + */ +@Getter +public enum DatasourceContainerExceptionEnum implements AbstractExceptionEnum { + + /** + * 查询库中所有数据源信息错误 + */ + QUERY_DBS_DAO_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "01", "查询库中所有数据源信息错误,具体错误为:{}"), + + /** + * 插入数据源信息错误 + */ + INSERT_DBS_DAO_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "02", "插入数据源信息错误,具体错误为:{}"), + + /** + * 删除数据源信息错误 + */ + DELETE_DBS_DAO_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "03", "删除数据源信息错误,具体错误为:{}"), + + /** + * 根据数据库查询结果,创建DruidProperties失败 + */ + CREATE_PROP_DAO_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "04", "根据数据库查询结果,创建DruidProperties失败,具体错误为:{}"), + + /** + * 数据源连接信息存在空值,无法从初始化数据源容器 + */ + DB_CONNECTION_INFO_EMPTY_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "05", "数据源连接信息存在空值,无法从初始化数据源容器,url:{},username:{},pwd:***"), + + /** + * 初始化数据源容器异常 + */ + INIT_DATASOURCE_CONTAINER_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "06", "初始化数据源容器异常,具体错误为:{}"), + + /** + * 检验数据库连接失败 + */ + VALIDATE_DATASOURCE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "07", "检验数据库连接失败,请检查连接是否可用,url为:{}"), + + /** + * 添加数据源失败,当前环境已经存在同名数据源 + */ + DATASOURCE_NAME_REPEAT(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "08", "添加数据源失败,当前环境已经存在同名数据源,dbName为:{}"), + + /** + * 初始化数据源异常 + */ + INIT_DATASOURCE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "09", "初始化数据源异常"), + + /** + * 修改数据源失败,所传参数数据源不存在 + */ + EDIT_DATASOURCE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "10", "修改数据源失败,所传参数数据源不存在,id={}"), + + /** + * 修改数据源失败,不能修改数据源名称 + */ + EDIT_DATASOURCE_NAME_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "10", "修改数据源失败,不能修改数据源名称,原名称为={}"), + + /** + * 删除数据源失败,原数据不存在 + */ + DELETE_DATASOURCE_NOT_EXISTED_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "10", "修改数据源失败,不能修改数据源名称,原名称为={}"), + + /** + * 租户数据源不能删除 + */ + TENANT_DATASOURCE_CANT_DELETE(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "11", "租户数据源不能删除"), + + /** + * 主数据源不能删除 + */ + MASTER_DATASOURCE_CANT_DELETE(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DatasourceContainerConstants.DS_CTN_EXCEPTION_STEP_CODE + "12", "主数据源不能删除,会导致当前程序崩溃"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + DatasourceContainerExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-ds-container/ds-container-business/README.md b/kernel-d-ds-container/ds-container-business/README.md new file mode 100644 index 000000000..f7e8552ab --- /dev/null +++ b/kernel-d-ds-container/ds-container-business/README.md @@ -0,0 +1 @@ +数据源容器的业务模块,在线维护数据源信息 \ No newline at end of file diff --git a/kernel-d-ds-container/ds-container-business/pom.xml b/kernel-d-ds-container/ds-container-business/pom.xml new file mode 100644 index 000000000..a716ab72a --- /dev/null +++ b/kernel-d-ds-container/ds-container-business/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-ds-container + 1.0.0 + ../pom.xml + + + ds-container-business + + jar + + + + + + cn.stylefeng.roses + ds-container-sdk + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/controller/DatabaseInfoController.java b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/controller/DatabaseInfoController.java new file mode 100644 index 000000000..fc0a47fa7 --- /dev/null +++ b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/controller/DatabaseInfoController.java @@ -0,0 +1,83 @@ +package cn.stylefeng.roses.kernel.dsctn.modular.controller; + +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.dsctn.modular.entity.DatabaseInfo; +import cn.stylefeng.roses.kernel.dsctn.modular.pojo.DatabaseInfoParam; +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.dsctn.modular.service.DatabaseInfoService; +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + + +/** + * 数据库信息表控制器 + * + * @author fengshuonan + * @date 2020/11/1 22:15 + */ +@RestController +@ApiResource(name = "数据源信息管理") +public class DatabaseInfoController { + + @Resource + private DatabaseInfoService databaseInfoService; + + /** + * 新增数据源 + * + * @author fengshuonan + * @date 2020/11/1 22:16 + */ + @PostResource(name = "新增数据源", path = "/databaseInfo/add") + public ResponseData add(@RequestBody @Validated(BaseRequest.add.class) DatabaseInfoParam databaseInfoParam) { + databaseInfoService.add(databaseInfoParam); + return new SuccessResponseData(); + } + + /** + * 编辑数据源 + * + * @author fengshuonan + * @date 2020/11/1 22:16 + */ + @PostResource(name = "编辑数据源", path = "/databaseInfo/edit") + public ResponseData edit(@RequestBody @Validated(DatabaseInfoParam.edit.class) DatabaseInfoParam databaseInfoParam) { + databaseInfoService.edit(databaseInfoParam); + return new SuccessResponseData(); + } + + /** + * 删除数据源 + * + * @author fengshuonan + * @date 2020/11/1 22:18 + */ + @PostResource(name = "删除数据源", path = "/databaseInfo/delete") + public ResponseData delete(@RequestBody @Validated(DatabaseInfoParam.delete.class) DatabaseInfoParam databaseInfoParam) { + databaseInfoService.delete(databaseInfoParam); + return new SuccessResponseData(); + } + + /** + * 查询数据源列表(带分页) + * + * @author fengshuonan + * @date 2020/11/1 22:18 + */ + @GetResource(name = "查询数据源列表(带分页)", path = "/databaseInfo/page") + public ResponseData page(DatabaseInfoParam databaseInfoParam) { + PageResult pageResult = databaseInfoService.page(databaseInfoParam); + return new SuccessResponseData(pageResult); + } + +} + + diff --git a/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/entity/DatabaseInfo.java b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/entity/DatabaseInfo.java new file mode 100644 index 000000000..62712521e --- /dev/null +++ b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/entity/DatabaseInfo.java @@ -0,0 +1,66 @@ +package cn.stylefeng.roses.kernel.dsctn.modular.entity; + +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 cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 数据库信息表 + * + * @author fengshuonan + * @date 2020/11/1 0:15 + */ +@TableName("sys_database_info") +@Data +@EqualsAndHashCode(callSuper = true) +public class DatabaseInfo extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 数据库名称(英文名称) + */ + @TableField("db_name") + private String dbName; + + /** + * jdbc的驱动类型 + */ + @TableField("jdbc_driver") + private String jdbcDriver; + + /** + * 数据库连接的账号 + */ + @TableField("user_name") + private String userName; + + /** + * 数据库连接密码 + */ + @TableField("password") + private String password; + + /** + * jdbc的url + */ + @TableField("jdbc_url") + private String jdbcUrl; + + /** + * 备注,摘要 + */ + @TableField("remarks") + private String remarks; + +} diff --git a/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/factory/DruidFactory.java b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/factory/DruidFactory.java new file mode 100644 index 000000000..e3b967e2b --- /dev/null +++ b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/factory/DruidFactory.java @@ -0,0 +1,29 @@ +package cn.stylefeng.roses.kernel.dsctn.modular.factory; + +import cn.stylefeng.roses.kernel.db.api.pojo.druid.DruidProperties; +import cn.stylefeng.roses.kernel.dsctn.modular.entity.DatabaseInfo; + +/** + * Druid数据源创建 + * + * @author fengshuonan + * @date 2020/11/1 21:44 + */ +public class DruidFactory { + + /** + * 创建druid配置 + * + * @author fengshuonan + * @date 2019-06-15 20:05 + */ + public static DruidProperties createDruidProperties(DatabaseInfo databaseInfo) { + DruidProperties druidProperties = new DruidProperties(); + druidProperties.setDriverClassName(databaseInfo.getJdbcDriver()); + druidProperties.setUsername(databaseInfo.getUserName()); + druidProperties.setPassword(databaseInfo.getPassword()); + druidProperties.setUrl(databaseInfo.getJdbcUrl()); + return druidProperties; + } + +} diff --git a/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/mapper/DatabaseInfoMapper.java b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/mapper/DatabaseInfoMapper.java new file mode 100644 index 000000000..bb8f01264 --- /dev/null +++ b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/mapper/DatabaseInfoMapper.java @@ -0,0 +1,14 @@ +package cn.stylefeng.roses.kernel.dsctn.modular.mapper; + +import cn.stylefeng.roses.kernel.dsctn.modular.entity.DatabaseInfo; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 数据库信息表 Mapper 接口 + * + * @author fengshuonan + * @date 2020/11/18 22:59 + */ +public interface DatabaseInfoMapper extends BaseMapper { + +} diff --git a/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/mapper/mapping/DatabaseInfoMapper.xml b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/mapper/mapping/DatabaseInfoMapper.xml new file mode 100644 index 000000000..7c29ce3a5 --- /dev/null +++ b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/mapper/mapping/DatabaseInfoMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/pojo/DatabaseInfoParam.java b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/pojo/DatabaseInfoParam.java new file mode 100644 index 000000000..640a36335 --- /dev/null +++ b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/pojo/DatabaseInfoParam.java @@ -0,0 +1,75 @@ +package cn.stylefeng.roses.kernel.dsctn.modular.pojo; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.validator.validators.unique.TableUniqueValue; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.Date; + +/** + * 数据库信息表 + * + * @author fengshuonan + * @date 2020/11/1 21:45 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class DatabaseInfoParam extends BaseRequest { + + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class}) + private Long id; + + /** + * 数据库名称(英文名称) + */ + @NotBlank(message = "数据库名称不能为空,请检查dbName参数", groups = {add.class, edit.class}) + @TableUniqueValue( + message = "数据库名称存在重复,请检查dbName参数", + groups = {add.class, edit.class}, + tableName = "sys_database_info", + columnName = "db_name") + private String dbName; + + /** + * jdbc的驱动类型 + */ + @NotBlank(message = "jdbc的驱动类型,请检查jdbcDriver参数", groups = {add.class, edit.class}) + private String jdbcDriver; + + /** + * 数据库连接的账号 + */ + @NotBlank(message = "数据库连接的账号,请检查userName参数", groups = {add.class, edit.class}) + private String userName; + + /** + * 数据库连接密码 + */ + @NotBlank(message = "数据库连接密码,请检查password参数", groups = {add.class, edit.class}) + private String password; + + /** + * jdbc的url + */ + @NotBlank(message = "jdbc的url,请检查jdbcUrl参数", groups = {add.class, edit.class}) + private String jdbcUrl; + + /** + * 备注,摘要 + */ + private String remarks; + + /** + * 创建时间 + */ + private Date createTime; + +} diff --git a/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/service/DatabaseInfoService.java b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/service/DatabaseInfoService.java new file mode 100644 index 000000000..62b270210 --- /dev/null +++ b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/service/DatabaseInfoService.java @@ -0,0 +1,52 @@ +package cn.stylefeng.roses.kernel.dsctn.modular.service; + +import cn.stylefeng.roses.kernel.dsctn.modular.entity.DatabaseInfo; +import cn.stylefeng.roses.kernel.dsctn.modular.pojo.DatabaseInfoParam; +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; + +/** + * 数据库信息表 服务类 + * + * @author fengshuonan + * @date 2020/11/1 21:46 + */ +public interface DatabaseInfoService extends IService { + + /** + * 新增数据库信息 + * + * @param databaseInfoParam 新增参数 + * @author fengshuonan + * @date 2020/11/1 21:47 + */ + void add(DatabaseInfoParam databaseInfoParam); + + /** + * 编辑数据库信息 + * + * @param databaseInfoParam 编辑参数 + * @author fengshuonan + * @date 2020/11/1 21:47 + */ + void edit(DatabaseInfoParam databaseInfoParam); + + /** + * 删除,删除会导致某些用该数据源的service操作失败 + * + * @param databaseInfoParam 删除参数 + * @author fengshuonan + * @date 2020/11/1 21:47 + */ + void delete(DatabaseInfoParam databaseInfoParam); + + /** + * 查询数据库信息 + * + * @param databaseInfoParam 查询参数 + * @return 查询分页结果 + * @author fengshuonan + * @date 2020/11/1 21:47 + */ + PageResult page(DatabaseInfoParam databaseInfoParam); +} diff --git a/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/service/impl/DatabaseInfoServiceImpl.java b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/service/impl/DatabaseInfoServiceImpl.java new file mode 100644 index 000000000..9f2852873 --- /dev/null +++ b/kernel-d-ds-container/ds-container-business/src/main/java/cn/stylefeng/roses/kernel/dsctn/modular/service/impl/DatabaseInfoServiceImpl.java @@ -0,0 +1,203 @@ +package cn.stylefeng.roses.kernel.dsctn.modular.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.dsctn.modular.entity.DatabaseInfo; +import cn.stylefeng.roses.kernel.dsctn.modular.factory.DruidFactory; +import com.alibaba.druid.pool.DruidDataSource; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.druid.DruidProperties; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.dsctn.api.exception.DatasourceContainerException; +import cn.stylefeng.roses.kernel.dsctn.context.DataSourceContext; +import cn.stylefeng.roses.kernel.dsctn.modular.mapper.DatabaseInfoMapper; +import cn.stylefeng.roses.kernel.dsctn.modular.pojo.DatabaseInfoParam; +import cn.stylefeng.roses.kernel.dsctn.modular.service.DatabaseInfoService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.List; + +import static cn.stylefeng.roses.kernel.dsctn.api.constants.DatasourceContainerConstants.MASTER_DATASOURCE_NAME; +import static cn.stylefeng.roses.kernel.dsctn.api.constants.DatasourceContainerConstants.TENANT_DB_PREFIX; +import static cn.stylefeng.roses.kernel.dsctn.api.exception.enums.DatasourceContainerExceptionEnum.*; + +/** + * 数据库信息表 服务实现类 + * + * @author fengshuonan + * @date 2020/11/1 21:45 + */ +@Service +public class DatabaseInfoServiceImpl extends ServiceImpl implements DatabaseInfoService { + + @Override + @Transactional(rollbackFor = Exception.class) + public void add(DatabaseInfoParam databaseInfoParam) { + + // 判断数据库连接是否可用 + validateConnection(databaseInfoParam); + + // 数据库中插入记录 + DatabaseInfo entity = parseEntity(databaseInfoParam); + this.save(entity); + + // 先判断context中是否有了这个数据源 + DataSource dataSource = DataSourceContext.getDataSources().get(databaseInfoParam.getDbName()); + if (dataSource != null) { + String userTip = StrUtil.format(DATASOURCE_NAME_REPEAT.getUserTip(), databaseInfoParam.getDbName()); + throw new DatasourceContainerException(DATASOURCE_NAME_REPEAT, userTip); + } + + // 往上下文中添加数据源 + DruidProperties druidProperties = DruidFactory.createDruidProperties(entity); + DruidDataSource druidDataSource = cn.stylefeng.roses.kernel.db.api.factory.DruidFactory.createDruidDataSource(druidProperties); + DataSourceContext.addDataSource(databaseInfoParam.getDbName(), druidDataSource, druidProperties); + + // 初始化数据源 + try { + druidDataSource.init(); + } catch (SQLException exception) { + log.error("初始化数据源异常!", exception); + String userTip = StrUtil.format(INIT_DATASOURCE_ERROR.getUserTip(), exception.getMessage()); + throw new DatasourceContainerException(INIT_DATASOURCE_ERROR, userTip); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void edit(DatabaseInfoParam databaseInfoParam) { + + DatabaseInfo oldEntity = this.getById(databaseInfoParam.getId()); + if (oldEntity == null) { + String userTip = StrUtil.format(EDIT_DATASOURCE_ERROR.getUserTip(), databaseInfoParam.getId()); + throw new DatasourceContainerException(EDIT_DATASOURCE_ERROR, userTip); + } + + // 不能修改数据源的名称 + if (!databaseInfoParam.getDbName().equals(oldEntity.getDbName())) { + String userTip = StrUtil.format(EDIT_DATASOURCE_NAME_ERROR.getUserTip(), oldEntity.getDbName()); + throw new DatasourceContainerException(EDIT_DATASOURCE_NAME_ERROR, userTip); + } + + // 判断数据库连接是否可用 + validateConnection(databaseInfoParam); + + // 更新库中的记录 + BeanUtil.copyProperties(databaseInfoParam, oldEntity); + this.updateById(oldEntity); + + // 删除容器中的数据源记录 + DataSourceContext.removeDataSource(oldEntity.getDbName()); + + // 往上下文中添加数据源 + DruidProperties druidProperties = DruidFactory.createDruidProperties(oldEntity); + DruidDataSource druidDataSource = cn.stylefeng.roses.kernel.db.api.factory.DruidFactory.createDruidDataSource(druidProperties); + DataSourceContext.addDataSource(databaseInfoParam.getDbName(), druidDataSource, druidProperties); + + // 初始化数据源 + try { + druidDataSource.init(); + } catch (SQLException exception) { + log.error("初始化数据源异常!", exception); + String userTip = StrUtil.format(INIT_DATASOURCE_ERROR.getUserTip(), exception.getMessage()); + throw new DatasourceContainerException(INIT_DATASOURCE_ERROR, userTip); + } + + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(DatabaseInfoParam param) { + + DatabaseInfo databaseInfo = this.getById(param.getId()); + if (databaseInfo == null) { + String userTip = StrUtil.format(DELETE_DATASOURCE_NOT_EXISTED_ERROR.getUserTip(), param.getId()); + throw new DatasourceContainerException(DELETE_DATASOURCE_NOT_EXISTED_ERROR, userTip); + } + + // 如果是租户数据库不能删除 + if (databaseInfo.getDbName().startsWith(TENANT_DB_PREFIX)) { + throw new DatasourceContainerException(TENANT_DATASOURCE_CANT_DELETE); + } + + // 不能删除主数据源 + if (MASTER_DATASOURCE_NAME.equals(databaseInfo.getDbName())) { + throw new DatasourceContainerException(MASTER_DATASOURCE_CANT_DELETE); + } + + // 删除库中的数据源记录 + this.removeById(param.getId()); + + // 删除容器中的数据源记录 + DataSourceContext.removeDataSource(databaseInfo.getDbName()); + } + + @Override + public PageResult page(DatabaseInfoParam databaseInfoParam) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 根据名称模糊查询 + if (ObjectUtil.isNotNull(databaseInfoParam) && ObjectUtil.isNotEmpty(databaseInfoParam.getDbName())) { + queryWrapper.like(DatabaseInfo::getDbName, databaseInfoParam.getDbName()); + } + + // 查询分页结果 + Page result = this.page(PageFactory.defaultPage(), queryWrapper); + + // 更新密码 + List records = result.getRecords(); + for (DatabaseInfo record : records) { + record.setPassword("***"); + } + + return PageResultFactory.createPageResult(result); + } + + /** + * 判断数据库连接是否可用 + * + * @author fengshuonan + * @date 2020/11/1 21:50 + */ + private void validateConnection(DatabaseInfoParam param) { + Connection conn = null; + try { + Class.forName(param.getJdbcDriver()); + conn = DriverManager.getConnection(param.getJdbcUrl(), param.getUserName(), param.getPassword()); + } catch (Exception e) { + String userTip = StrUtil.format(VALIDATE_DATASOURCE_ERROR.getUserTip(), param.getJdbcUrl()); + throw new DatasourceContainerException(VALIDATE_DATASOURCE_ERROR, userTip); + } finally { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } + } + + /** + * param转化为实体 + * + * @author fengshuonan + * @date 2020/11/1 21:50 + */ + private DatabaseInfo parseEntity(DatabaseInfoParam param) { + DatabaseInfo entity = new DatabaseInfo(); + BeanUtil.copyProperties(param, entity); + return entity; + } + +} diff --git a/kernel-d-ds-container/ds-container-sdk/README.md b/kernel-d-ds-container/ds-container-sdk/README.md new file mode 100644 index 000000000..d0f0dab47 --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/README.md @@ -0,0 +1 @@ +多数据源模块的sdk \ No newline at end of file diff --git a/kernel-d-ds-container/ds-container-sdk/pom.xml b/kernel-d-ds-container/ds-container-sdk/pom.xml new file mode 100644 index 000000000..104e88212 --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-ds-container + 1.0.0 + ../pom.xml + + + ds-container-sdk + + jar + + + + + + cn.stylefeng.roses + ds-container-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-api + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/AbstractRoutingDataSource.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/AbstractRoutingDataSource.java new file mode 100644 index 000000000..aa3bb9cec --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/AbstractRoutingDataSource.java @@ -0,0 +1,47 @@ +package cn.stylefeng.roses.kernel.dsctn; + +import org.springframework.jdbc.datasource.AbstractDataSource; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * 抽象动态获取数据源 + * + * @author TaoYu + * @since 2.2.0 + */ +public abstract class AbstractRoutingDataSource extends AbstractDataSource { + + /** + * 子类实现决定最终数据源 + * + * @return 数据源 + */ + protected abstract DataSource determineDataSource(); + + @Override + public Connection getConnection() throws SQLException { + return determineDataSource().getConnection(); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + return determineDataSource().getConnection(username, password); + } + + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class iface) throws SQLException { + if (iface.isInstance(this)) { + return (T) this; + } + return determineDataSource().unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return (iface.isInstance(this) || determineDataSource().isWrapperFor(iface)); + } +} diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/DynamicDataSource.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/DynamicDataSource.java new file mode 100644 index 000000000..f757ab45f --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/DynamicDataSource.java @@ -0,0 +1,42 @@ +package cn.stylefeng.roses.kernel.dsctn; + +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.dsctn.api.context.CurrentDataSourceContext; +import cn.stylefeng.roses.kernel.dsctn.context.DataSourceContext; + +import javax.sql.DataSource; +import java.util.Map; + +import static cn.stylefeng.roses.kernel.dsctn.api.constants.DatasourceContainerConstants.MASTER_DATASOURCE_NAME; + +/** + * 动态数据源的实现,带切换功能的 + * + * @author fengshuonan + * @date 2020/11/1 0:08 + */ +public class DynamicDataSource extends AbstractRoutingDataSource { + + /** + * 决断当前正在进行的service或者mapper用哪个数据源 + * + * @author fengshuonan + * @date 2020/11/1 0:08 + */ + @Override + protected DataSource determineDataSource() { + + // 获取当前Context存储的数据源名称 + String dataSourceName = CurrentDataSourceContext.getDataSourceName(); + + // 如果当前Context没有值,就用主数据源 + if (StrUtil.isEmpty(dataSourceName)) { + dataSourceName = MASTER_DATASOURCE_NAME; + } + + // 从数据源容器中获取对应的数据源 + Map dataSources = DataSourceContext.getDataSources(); + return dataSources.get(dataSourceName); + } + +} diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/aop/MultiSourceExchangeAop.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/aop/MultiSourceExchangeAop.java new file mode 100644 index 000000000..f71602e2a --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/aop/MultiSourceExchangeAop.java @@ -0,0 +1,79 @@ +package cn.stylefeng.roses.kernel.dsctn.aop; + +import cn.stylefeng.roses.kernel.dsctn.api.annotation.DataSource; +import cn.stylefeng.roses.kernel.dsctn.api.context.CurrentDataSourceContext; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.Ordered; + +import java.lang.reflect.Method; + +import static cn.stylefeng.roses.kernel.dsctn.api.constants.DatasourceContainerConstants.MASTER_DATASOURCE_NAME; +import static cn.stylefeng.roses.kernel.dsctn.api.constants.DatasourceContainerConstants.MULTI_DATA_SOURCE_EXCHANGE_AOP; + + +/** + * 多数据源切换的AOP,切的是 DataSource 注解 + * + * @author fengshuonan + * @date 2020/10/31 22:55 + */ +@Aspect +@Slf4j +public class MultiSourceExchangeAop implements Ordered { + + @Pointcut(value = "@annotation(cn.stylefeng.roses.kernel.dsctn.api.annotation.DataSource)") + private void cutService() { + + } + + @Around("cutService()") + public Object around(ProceedingJoinPoint point) throws Throwable { + + // 获取被aop拦截的方法 + Signature signature = point.getSignature(); + MethodSignature methodSignature = null; + if (!(signature instanceof MethodSignature)) { + throw new IllegalArgumentException("该注解只能用于方法"); + } + methodSignature = (MethodSignature) signature; + Object target = point.getTarget(); + Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes()); + + // 获取方法上的DataSource注解 + DataSource datasource = currentMethod.getAnnotation(DataSource.class); + + // 如果有DataSource注解,则设置DataSourceContextHolder为注解上的名称 + if (datasource != null) { + CurrentDataSourceContext.setDataSourceName(datasource.name()); + log.debug(">>> 设置数据源为:" + datasource.name()); + } else { + CurrentDataSourceContext.setDataSourceName(MASTER_DATASOURCE_NAME); + log.debug(">>> 设置数据源为:dataSourceCurrent"); + } + + try { + return point.proceed(); + } finally { + log.debug(">>> 清空数据源信息!"); + CurrentDataSourceContext.clearDataSourceName(); + } + } + + /** + * aop的顺序要早于spring的事务 + * + * @author fengshuonan + * @date 2020/10/31 22:55 + */ + @Override + public int getOrder() { + return MULTI_DATA_SOURCE_EXCHANGE_AOP; + } + +} diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/context/DataSourceContext.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/context/DataSourceContext.java new file mode 100644 index 000000000..2ac94cc22 --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/context/DataSourceContext.java @@ -0,0 +1,142 @@ +package cn.stylefeng.roses.kernel.dsctn.context; + + +import cn.stylefeng.roses.kernel.dsctn.persist.DataBaseInfoPersistence; +import com.alibaba.druid.pool.DruidDataSource; +import cn.stylefeng.roses.kernel.db.api.factory.DruidFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.druid.DruidProperties; + +import javax.sql.DataSource; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static cn.stylefeng.roses.kernel.dsctn.api.constants.DatasourceContainerConstants.MASTER_DATASOURCE_NAME; + + +/** + * 存放数据源容和数据源配置的容器 + * + * @author fengshuonan + * @date 2020/10/31 23:40 + */ +public class DataSourceContext { + + /** + * 数据源容器 + *

+ * key是数据源名称 + */ + private static final Map DATA_SOURCES = new ConcurrentHashMap<>(); + + /** + * 数据源的配置容器 + *

+ * key是数据源名称 + */ + private static Map DATA_SOURCES_CONF = new ConcurrentHashMap<>(); + + /** + * 初始化所有dataSource + * + * @author fengshuonan + * @date 2020/10/31 23:40 + */ + public static void initDataSource(DruidProperties masterDataSourceProperties, DataSource dataSourcePrimary) { + + // 清空数据库中的主数据源信息 + new DataBaseInfoPersistence(masterDataSourceProperties).deleteMasterDatabaseInfo(); + + // 初始化主数据源信息 + new DataBaseInfoPersistence(masterDataSourceProperties).createMasterDatabaseInfo(); + + // 从数据库中获取所有的数据源信息 + DataBaseInfoPersistence dataBaseInfoDao = new DataBaseInfoPersistence(masterDataSourceProperties); + Map allDataBaseInfo = dataBaseInfoDao.getAllDataBaseInfo(); + + // 赋给全局变量 + DATA_SOURCES_CONF = allDataBaseInfo; + + // 根据数据源信息初始化所有的DataSource + for (Map.Entry entry : allDataBaseInfo.entrySet()) { + + String dbName = entry.getKey(); + DruidProperties druidProperties = entry.getValue(); + + // 如果是主数据源,不用初始化第二遍,如果是其他数据源就通过property初始化 + if (dbName.equalsIgnoreCase(MASTER_DATASOURCE_NAME)) { + DATA_SOURCES_CONF.put(dbName, druidProperties); + DATA_SOURCES.put(dbName, dataSourcePrimary); + } else { + DataSource dataSource = createDataSource(dbName, druidProperties); + DATA_SOURCES.put(dbName, dataSource); + } + } + } + + /** + * 新增数据源到容器中 + * + * @author fengshuonan + * @date 2020/10/31 23:40 + */ + public static void addDataSource(String dbName, DataSource dataSource, DruidProperties druidProperties) { + DATA_SOURCES.put(dbName, dataSource); + DATA_SOURCES_CONF.put(dbName, druidProperties); + } + + /** + * 获取所有数据源 + * + * @author fengshuonan + * @date 2020/10/31 23:42 + */ + public static Map getDataSources() { + return DATA_SOURCES; + } + + /** + * 获取数据源的配置 + * + * @author fengshuonan + * @date 2020/10/31 23:42 + */ + public static Map getDataSourcesConfs() { + return DATA_SOURCES_CONF; + } + + /** + * 删除容器中的数据源 + * + * @author fengshuonan + * @date 2020/10/31 23:44 + */ + public static void removeDataSource(String dataSourceName) { + + // 先关闭掉数据源 + DataSource dataSource = DATA_SOURCES.get(dataSourceName); + if (dataSource instanceof DruidDataSource) { + ((DruidDataSource) dataSource).close(); + } + + // 从容器中删除数据源 + DATA_SOURCES.remove(dataSourceName); + + // 多数据源配置中也删除 + DATA_SOURCES_CONF.remove(dataSourceName); + } + + /** + * 创建数据源,但是不添加到容器中 + * + * @author fengshuonan + * @date 2020/10/31 23:43 + */ + private static DataSource createDataSource(String dataSourceName, DruidProperties druidProperties) { + + //添加到全局配置里 + DATA_SOURCES_CONF.put(dataSourceName, druidProperties); + + return DruidFactory.createDruidDataSource(druidProperties); + } + +} diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/listener/DataSourceInitListener.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/listener/DataSourceInitListener.java new file mode 100644 index 000000000..7f2477f7c --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/listener/DataSourceInitListener.java @@ -0,0 +1,79 @@ +package cn.stylefeng.roses.kernel.dsctn.listener; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.dsctn.context.DataSourceContext; +import com.alibaba.druid.pool.DruidDataSource; +import cn.stylefeng.roses.kernel.db.api.factory.DruidFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.druid.DruidProperties; +import cn.stylefeng.roses.kernel.dsctn.api.exception.DatasourceContainerException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationContextInitializedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; + +import static cn.stylefeng.roses.kernel.dsctn.api.exception.enums.DatasourceContainerExceptionEnum.DB_CONNECTION_INFO_EMPTY_ERROR; +import static cn.stylefeng.roses.kernel.dsctn.api.exception.enums.DatasourceContainerExceptionEnum.INIT_DATASOURCE_CONTAINER_ERROR; + + +/** + * 多数据源的初始化,加入到数据源Context中的过程 + * + * @author fengshuonan + * @date 2020/11/1 0:02 + */ +@Slf4j +public class DataSourceInitListener implements ApplicationListener, Ordered { + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + @Override + public void onApplicationEvent(ApplicationContextInitializedEvent applicationContextInitializedEvent) { + + // 如果是配置中心的上下文略过,spring cloud环境environment会读取不到 + ConfigurableApplicationContext applicationContext = applicationContextInitializedEvent.getApplicationContext(); + if (applicationContext instanceof AnnotationConfigApplicationContext) { + return; + } + + ConfigurableEnvironment environment = applicationContextInitializedEvent.getApplicationContext().getEnvironment(); + + // 获取数据库连接配置 + String dataSourceDriver = environment.getProperty("spring.datasource.driver-class-name"); + String dataSourceUrl = environment.getProperty("spring.datasource.url"); + String dataSourceUsername = environment.getProperty("spring.datasource.username"); + String dataSourcePassword = environment.getProperty("spring.datasource.password"); + + // 如果有为空的配置,终止执行 + if (ObjectUtil.hasEmpty(dataSourceUrl, dataSourceUsername, dataSourcePassword)) { + String userTip = StrUtil.format(DB_CONNECTION_INFO_EMPTY_ERROR.getUserTip(), dataSourceUrl, dataSourceUsername); + throw new DatasourceContainerException(DB_CONNECTION_INFO_EMPTY_ERROR, userTip); + } + + // 创建主数据源的properties + DruidProperties druidProperties = new DruidProperties(); + druidProperties.setDriverClassName(dataSourceDriver); + druidProperties.setUrl(dataSourceUrl); + druidProperties.setUsername(dataSourceUsername); + druidProperties.setPassword(dataSourcePassword); + + // 创建主数据源 + DruidDataSource druidDataSource = DruidFactory.createDruidDataSource(druidProperties); + + // 初始化数据源容器 + try { + DataSourceContext.initDataSource(druidProperties, druidDataSource); + } catch (Exception exception) { + log.error(">>> 初始化数据源容器错误!", exception); + String userTip = StrUtil.format(INIT_DATASOURCE_CONTAINER_ERROR.getUserTip(), exception.getMessage()); + throw new DatasourceContainerException(INIT_DATASOURCE_CONTAINER_ERROR, userTip); + } + + } +} diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/DataBaseInfoPersistence.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/DataBaseInfoPersistence.java new file mode 100644 index 000000000..d8b394c1e --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/DataBaseInfoPersistence.java @@ -0,0 +1,150 @@ +package cn.stylefeng.roses.kernel.dsctn.persist; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.dsctn.persist.sqls.AddDatabaseInfoSql; +import cn.stylefeng.roses.kernel.dsctn.persist.sqls.DatabaseListSql; +import cn.stylefeng.roses.kernel.dsctn.persist.sqls.DeleteDatabaseInfoSql; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import cn.stylefeng.roses.kernel.db.api.pojo.druid.DruidProperties; +import cn.stylefeng.roses.kernel.dsctn.api.exception.DatasourceContainerException; +import lombok.extern.slf4j.Slf4j; + +import java.sql.*; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static cn.stylefeng.roses.kernel.dsctn.api.constants.DatasourceContainerConstants.MASTER_DATASOURCE_NAME; +import static cn.stylefeng.roses.kernel.dsctn.api.exception.enums.DatasourceContainerExceptionEnum.*; + + +/** + * 数据源信息相关操作的dao + * + * @author fengshuonan + * @date 2020/10/31 23:47 + */ +@Slf4j +public class DataBaseInfoPersistence { + + private final DruidProperties druidProperties; + + public DataBaseInfoPersistence(DruidProperties druidProperties) { + this.druidProperties = druidProperties; + } + + /** + * 查询所有数据源列表 + * + * @author fengshuonan + * @date 2020/10/31 23:55 + */ + public Map getAllDataBaseInfo() { + Map dataSourceList = new HashMap<>(16); + try { + Class.forName(druidProperties.getDriverClassName()); + Connection conn = DriverManager.getConnection( + druidProperties.getUrl(), druidProperties.getUsername(), druidProperties.getPassword()); + + PreparedStatement preparedStatement = conn.prepareStatement(new DatabaseListSql().getSql(druidProperties.getUrl())); + ResultSet resultSet = preparedStatement.executeQuery(); + + while (resultSet.next()) { + DruidProperties druidProperties = createDruidProperties(resultSet); + String dbName = resultSet.getString("db_name"); + dataSourceList.put(dbName, druidProperties); + } + + return dataSourceList; + + } catch (Exception exception) { + log.error(">>> 查询数据源信息错误!", exception); + String userTip = StrUtil.format(QUERY_DBS_DAO_ERROR.getUserTip(), exception.getMessage()); + throw new DatasourceContainerException(QUERY_DBS_DAO_ERROR, userTip); + } + } + + /** + * 初始化master的数据源,要和properties配置的数据源一致 + * + * @author fengshuonan + * @date 2020/10/31 23:55 + */ + public void createMasterDatabaseInfo() { + try { + Class.forName(druidProperties.getDriverClassName()); + Connection conn = DriverManager.getConnection( + druidProperties.getUrl(), druidProperties.getUsername(), druidProperties.getPassword()); + + PreparedStatement preparedStatement = conn.prepareStatement(new AddDatabaseInfoSql().getSql(druidProperties.getUrl())); + + preparedStatement.setLong(1, IdWorker.getId()); + preparedStatement.setString(2, MASTER_DATASOURCE_NAME); + preparedStatement.setString(3, druidProperties.getDriverClassName()); + preparedStatement.setString(4, druidProperties.getUsername()); + preparedStatement.setString(5, druidProperties.getPassword()); + preparedStatement.setString(6, druidProperties.getUrl()); + preparedStatement.setString(7, "主数据源,项目启动数据源!"); + preparedStatement.setString(8, DateUtil.formatDateTime(new Date())); + + int i = preparedStatement.executeUpdate(); + log.info(">>> 初始化master的databaseInfo信息!初始化" + i + "条!"); + } catch (Exception exception) { + log.error(">>> 初始化master的databaseInfo信息错误!", exception); + String userTip = StrUtil.format(INSERT_DBS_DAO_ERROR.getUserTip(), exception.getMessage()); + throw new DatasourceContainerException(INSERT_DBS_DAO_ERROR, userTip); + } + } + + /** + * 删除master的数据源信息 + * + * @author fengshuonan + * @date 2020/10/31 23:55 + */ + public void deleteMasterDatabaseInfo() { + try { + Class.forName(druidProperties.getDriverClassName()); + Connection conn = DriverManager.getConnection( + druidProperties.getUrl(), druidProperties.getUsername(), druidProperties.getPassword()); + + PreparedStatement preparedStatement = conn.prepareStatement(new DeleteDatabaseInfoSql().getSql(druidProperties.getUrl())); + preparedStatement.setString(1, MASTER_DATASOURCE_NAME); + int i = preparedStatement.executeUpdate(); + log.info(">>> 删除master的databaseInfo信息!删除" + i + "条!"); + } catch (Exception exception) { + log.info(">>> 删除master的databaseInfo信息失败!", exception); + String userTip = StrUtil.format(DELETE_DBS_DAO_ERROR.getUserTip(), exception.getMessage()); + throw new DatasourceContainerException(DELETE_DBS_DAO_ERROR, userTip); + } + } + + /** + * 通过查询结果组装druidProperties + * + * @author fengshuonan + * @date 2020/10/31 23:55 + */ + private DruidProperties createDruidProperties(ResultSet resultSet) { + + DruidProperties druidProperties = new DruidProperties(); + + druidProperties.setTestOnBorrow(true); + druidProperties.setTestOnReturn(true); + + try { + druidProperties.setDriverClassName(resultSet.getString("jdbc_driver")); + druidProperties.setUrl(resultSet.getString("jdbc_url")); + druidProperties.setUsername(resultSet.getString("user_name")); + druidProperties.setPassword(resultSet.getString("password")); + } catch (SQLException exception) { + log.info(">>> 根据数据库查询结果,创建DruidProperties失败!", exception); + String userTip = StrUtil.format(CREATE_PROP_DAO_ERROR.getUserTip(), exception.getMessage()); + throw new DatasourceContainerException(CREATE_PROP_DAO_ERROR, userTip); + } + + return druidProperties; + } + +} diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/AbstractSql.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/AbstractSql.java new file mode 100644 index 000000000..7305c0f62 --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/AbstractSql.java @@ -0,0 +1,70 @@ +package cn.stylefeng.roses.kernel.dsctn.persist.sqls; + +import cn.stylefeng.roses.kernel.db.api.enums.DbTypeEnum; + +/** + * 异构sql获取 + * + * @author fengshuonan + * @date 2020/10/31 23:44 + */ +public abstract class AbstractSql { + + /** + * 获取异构sql + * + * @param jdbcUrl 数据连接的url + * @return 具体的sql + * @author fengshuonan + * @date 2020/10/31 23:44 + */ + public String getSql(String jdbcUrl) { + if (jdbcUrl.contains(DbTypeEnum.ORACLE.getCode())) { + return oracle(); + } + if (jdbcUrl.contains(DbTypeEnum.MS_SQL.getCode())) { + return sqlServer(); + } + if (jdbcUrl.contains(DbTypeEnum.PG_SQL.getCode())) { + return pgSql(); + } + return mysql(); + } + + /** + * 获取mysql的sql语句 + * + * @return 具体的sql + * @author fengshuonan + * @date 2020/10/31 23:45 + */ + protected abstract String mysql(); + + /** + * 获取sqlServer的sql语句 + * + * @return 具体的sql + * @author fengshuonan + * @date 2020/10/31 23:45 + */ + protected abstract String sqlServer(); + + /** + * 获取pgSql的sql语句 + * + * @return 具体的sql + * @author fengshuonan + * @date 2020/10/31 23:45 + */ + protected abstract String pgSql(); + + /** + * 获取oracle的sql语句 + * + * @return 具体的sql + * @author fengshuonan + * @date 2020/10/31 23:45 + */ + protected abstract String oracle(); + +} diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/AddDatabaseInfoSql.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/AddDatabaseInfoSql.java new file mode 100644 index 000000000..543e070e6 --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/AddDatabaseInfoSql.java @@ -0,0 +1,33 @@ +package cn.stylefeng.roses.kernel.dsctn.persist.sqls; + +import lombok.Getter; + +/** + * 添加数据源的sql + * + * @author fengshuonan + * @date 2019-07-16-13:06 + */ +@Getter +public class AddDatabaseInfoSql extends AbstractSql { + + @Override + protected String mysql() { + return "INSERT INTO `sys_database_info`(`id`, `db_name`, `jdbc_driver`, `user_name`, `password`, `jdbc_url`, `remarks`, `create_time`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; + } + + @Override + protected String sqlServer() { + return "INSERT INTO [sys_database_info] ([id], [db_name], [jdbc_driver], [user_name], [password], [jdbc_url], [remarks], [create_time]) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; + } + + @Override + protected String pgSql() { + return "INSERT INTO sys_database_info(id, db_name, jdbc_driver, user_name, password, jdbc_url, remarks, create_time) VALUES (?, ?, ?, ?, ?, ?, ?, to_timestamp(?,'YYYY-MM-DD HH24:MI:SS'))"; + } + + @Override + protected String oracle() { + return "INSERT INTO sys_database_info VALUES (?, ?, ?, ?, ?, ?, ?, to_date(?, 'yyyy-mm-dd hh24:mi:ss'))"; + } +} diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/CreateDatabaseSql.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/CreateDatabaseSql.java new file mode 100644 index 000000000..7fc4c2215 --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/CreateDatabaseSql.java @@ -0,0 +1,33 @@ +package cn.stylefeng.roses.kernel.dsctn.persist.sqls; + +import lombok.Getter; + +/** + * 创建数据库的sql + * + * @author fengshuonan + * @date 2019-07-16-13:06 + */ +@Getter +public class CreateDatabaseSql extends AbstractSql { + + @Override + protected String mysql() { + return "CREATE DATABASE IF NOT EXISTS ? DEFAULT CHARSET utf8 COLLATE utf8_general_ci;"; + } + + @Override + protected String sqlServer() { + return ""; + } + + @Override + protected String pgSql() { + return ""; + } + + @Override + protected String oracle() { + return ""; + } +} diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/DatabaseListSql.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/DatabaseListSql.java new file mode 100644 index 000000000..9db99e3bd --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/DatabaseListSql.java @@ -0,0 +1,33 @@ +package cn.stylefeng.roses.kernel.dsctn.persist.sqls; + +import lombok.Getter; + +/** + * 数据源列表sql + * + * @author fengshuonan + * @date 2019-07-16-13:06 + */ +@Getter +public class DatabaseListSql extends AbstractSql { + + @Override + protected String mysql() { + return "select db_name,jdbc_driver,jdbc_url,user_name,password from sys_database_info"; + } + + @Override + protected String sqlServer() { + return "select db_name,jdbc_driver,jdbc_url,user_name,password from sys_database_info"; + } + + @Override + protected String pgSql() { + return "select db_name,jdbc_driver,jdbc_url,user_name,password from sys_database_info"; + } + + @Override + protected String oracle() { + return "select db_name,jdbc_driver,jdbc_url,user_name,password from sys_database_info"; + } +} diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/DeleteDatabaseInfoSql.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/DeleteDatabaseInfoSql.java new file mode 100644 index 000000000..f82cedfb1 --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/DeleteDatabaseInfoSql.java @@ -0,0 +1,33 @@ +package cn.stylefeng.roses.kernel.dsctn.persist.sqls; + +import lombok.Getter; + +/** + * 删除数据源sql + * + * @author fengshuonan + * @date 2019-07-16-13:06 + */ +@Getter +public class DeleteDatabaseInfoSql extends AbstractSql { + + @Override + protected String mysql() { + return "DELETE from sys_database_info where db_name = ?"; + } + + @Override + protected String sqlServer() { + return "DELETE from sys_database_info where db_name = ?"; + } + + @Override + protected String pgSql() { + return "DELETE from sys_database_info where db_name = ?"; + } + + @Override + protected String oracle() { + return "DELETE from sys_database_info where db_name = ?"; + } +} diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/DropDatabaseSql.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/DropDatabaseSql.java new file mode 100644 index 000000000..26cf6ecba --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/DropDatabaseSql.java @@ -0,0 +1,33 @@ +package cn.stylefeng.roses.kernel.dsctn.persist.sqls; + +import lombok.Getter; + +/** + * 删除数据库的sql + * + * @author fengshuonan + * @date 2020/9/4 + */ +@Getter +public class DropDatabaseSql extends AbstractSql { + + @Override + protected String mysql() { + return "DROP DATABASE ?;"; + } + + @Override + protected String sqlServer() { + return ""; + } + + @Override + protected String pgSql() { + return ""; + } + + @Override + protected String oracle() { + return ""; + } +} diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/TableFieldListSql.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/TableFieldListSql.java new file mode 100644 index 000000000..fcd0cff57 --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/TableFieldListSql.java @@ -0,0 +1,49 @@ +package cn.stylefeng.roses.kernel.dsctn.persist.sqls; + +import lombok.Getter; + +/** + * 获取某个表的所有字段的sql + * + * @author fengshuonan + * @date 2019-07-16-13:06 + */ +@Getter +public class TableFieldListSql extends AbstractSql { + + @Override + protected String mysql() { + return "select COLUMN_NAME as columnName,COLUMN_COMMENT as columnComment from information_schema.COLUMNS where table_name = ? and table_schema = ?"; + } + + @Override + protected String sqlServer() { + return "SELECT A.name as columnName,\n" + + " CONVERT(varchar(200), isnull(G.[value], '')) as columnComment\n" + + " FROM syscolumns A\n" + + " Left Join systypes B On A.xusertype= B.xusertype\n" + + " Inner Join sysobjects D On A.id= D.id\n" + + " and D.xtype= 'U'\n" + + " and D.name<> 'dtproperties'\n" + + " Left Join syscomments E on A.cdefault= E.id\n" + + " Left Join sys.extended_properties G on A.id= G.major_id\n" + + " and A.colid= G.minor_id\n" + + " Left Join sys.extended_properties F On D.id= F.major_id\n" + + " and F.minor_id= 0\n" + + " where d.name= ? \n" + + " Order By A.id,\n" + + " A.colorder"; + } + + @Override + protected String pgSql() { + return "SELECT a.attname as \"columnName\" , col_description(a.attrelid,a.attnum) as \"columnComment\"\n" + + "FROM pg_class as c,pg_attribute as a " + + "where c.relname = ? and a.attrelid = c.oid and a.attnum>0"; + } + + @Override + protected String oracle() { + return "select column_name as columnName, comments as columnComment from user_col_comments where Table_Name= ?"; + } +} diff --git a/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/TableListSql.java b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/TableListSql.java new file mode 100644 index 000000000..7e92f9419 --- /dev/null +++ b/kernel-d-ds-container/ds-container-sdk/src/main/java/cn/stylefeng/roses/kernel/dsctn/persist/sqls/TableListSql.java @@ -0,0 +1,52 @@ +package cn.stylefeng.roses.kernel.dsctn.persist.sqls; + +import lombok.Getter; + +/** + * 获取所有表的sql + * + * @author fengshuonan + * @date 2019-07-16-13:06 + */ +@Getter +public class TableListSql extends AbstractSql { + + @Override + protected String mysql() { + return "select TABLE_NAME as tableName,TABLE_COMMENT as tableComment from information_schema.`TABLES` where TABLE_SCHEMA = ?"; + } + + @Override + protected String sqlServer() { + return "SELECT DISTINCT\n" + + "d.name as tableName,\n" + + "CONVERT(varchar(200), f.value) as tableComment\n" + + "FROM\n" + + "syscolumns a\n" + + "LEFT JOIN systypes b ON a.xusertype= b.xusertype\n" + + "INNER JOIN sysobjects d ON a.id= d.id\n" + + "AND d.xtype= 'U'\n" + + "AND d.name<> 'dtproperties'\n" + + "LEFT JOIN syscomments e ON a.cdefault= e.id\n" + + "LEFT JOIN sys.extended_properties g ON a.id= G.major_id\n" + + "AND a.colid= g.minor_id\n" + + "LEFT JOIN sys.extended_properties f ON d.id= f.major_id\n" + + "AND f.minor_id= 0"; + } + + @Override + protected String pgSql() { + return "select " + + "relname as \"tableName\"," + + "cast(obj_description(relfilenode,'pg_class') as varchar) as \"tableComment\" " + + "from pg_class c \n" + + "where relkind = 'r' and relname not like 'pg_%' and relname not like 'sql_%' order by relname"; + } + + @Override + protected String oracle() { + return "select ut.table_name as tableName, co.comments as tableComment from user_tables ut\n" + + "left join user_tab_comments co on ut.table_name = co.table_name\n" + + "where tablespace_name is not null and user= ?"; + } +} diff --git a/kernel-d-ds-container/ds-container-spring-boot-starter/README.md b/kernel-d-ds-container/ds-container-spring-boot-starter/README.md new file mode 100644 index 000000000..806fa18a6 --- /dev/null +++ b/kernel-d-ds-container/ds-container-spring-boot-starter/README.md @@ -0,0 +1 @@ +多数据源的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-ds-container/ds-container-spring-boot-starter/pom.xml b/kernel-d-ds-container/ds-container-spring-boot-starter/pom.xml new file mode 100644 index 000000000..15acf883b --- /dev/null +++ b/kernel-d-ds-container/ds-container-spring-boot-starter/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-ds-container + 1.0.0 + ../pom.xml + + + ds-container-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + ds-container-business + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-ds-container/ds-container-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/dsctn/starter/GunsDataSourceContainerAutoConfiguration.java b/kernel-d-ds-container/ds-container-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/dsctn/starter/GunsDataSourceContainerAutoConfiguration.java new file mode 100644 index 000000000..3b840928c --- /dev/null +++ b/kernel-d-ds-container/ds-container-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/dsctn/starter/GunsDataSourceContainerAutoConfiguration.java @@ -0,0 +1,41 @@ +package cn.stylefeng.roses.kernel.dsctn.starter; + +import cn.stylefeng.roses.kernel.dsctn.DynamicDataSource; +import cn.stylefeng.roses.kernel.dsctn.aop.MultiSourceExchangeAop; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 数据库连接和DAO框架的配置 + *

+ * 如果开启此连接池,注意排除 GunsDataSourceAutoConfiguration + * + * @author fengshuonan + * @date 2020/11/30 22:24 + */ +@Configuration +public class GunsDataSourceContainerAutoConfiguration { + + /** + * 多数据源连接池,如果开启此连接池,注意排除 GunsDataSourceAutoConfiguration + * + * @author fengshuonan + * @date 2020/11/30 22:49 + */ + @Bean + public DynamicDataSource dataSource() { + return new DynamicDataSource(); + } + + /** + * 数据源切换的AOP + * + * @author fengshuonan + * @date 2020/11/30 22:49 + */ + @Bean + public MultiSourceExchangeAop multiSourceExchangeAop() { + return new MultiSourceExchangeAop(); + } + +} diff --git a/kernel-d-ds-container/ds-container-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-ds-container/ds-container-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..702c0634f --- /dev/null +++ b/kernel-d-ds-container/ds-container-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.dsctn.starter.GunsDataSourceContainerAutoConfiguration \ No newline at end of file diff --git a/kernel-d-ds-container/pom.xml b/kernel-d-ds-container/pom.xml new file mode 100644 index 000000000..c4facdb28 --- /dev/null +++ b/kernel-d-ds-container/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-ds-container + + pom + + + ds-container-api + ds-container-sdk + ds-container-business + ds-container-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-email/README.md b/kernel-d-email/README.md new file mode 100644 index 000000000..87993b9df --- /dev/null +++ b/kernel-d-email/README.md @@ -0,0 +1 @@ +邮件发送模块 \ No newline at end of file diff --git a/kernel-d-email/email-api/README.md b/kernel-d-email/email-api/README.md new file mode 100644 index 000000000..3599c4b17 --- /dev/null +++ b/kernel-d-email/email-api/README.md @@ -0,0 +1 @@ +邮件api模块 \ No newline at end of file diff --git a/kernel-d-email/email-api/pom.xml b/kernel-d-email/email-api/pom.xml new file mode 100644 index 000000000..56881c615 --- /dev/null +++ b/kernel-d-email/email-api/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-email + 1.0.0 + ../pom.xml + + + email-api + + jar + + + + + + + cn.stylefeng.roses + config-api + 1.0.0 + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/MailSenderApi.java b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/MailSenderApi.java new file mode 100644 index 000000000..1c4b32f7f --- /dev/null +++ b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/MailSenderApi.java @@ -0,0 +1,31 @@ +package cn.stylefeng.roses.kernel.email.api; + +import cn.stylefeng.roses.kernel.email.api.pojo.SendMailParam; + +/** + * 邮件收发统一接口 + * + * @author fengshuonan + * @date 2020/10/23 17:30 + */ +public interface MailSenderApi { + + /** + * 发送普通邮件 + * + * @param sendMailParam 发送邮件的参数 + * @author fengshuonan + * @date 2020/10/23 17:30 + */ + void sendMail(SendMailParam sendMailParam); + + /** + * 发送html的邮件 + * + * @param sendMailParam 发送邮件的参数 + * @author fengshuonan + * @date 2020/10/23 17:30 + */ + void sendMailHtml(SendMailParam sendMailParam); + +} diff --git a/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/constants/MailConstants.java b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/constants/MailConstants.java new file mode 100644 index 000000000..926bbfe60 --- /dev/null +++ b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/constants/MailConstants.java @@ -0,0 +1,61 @@ +package cn.stylefeng.roses.kernel.email.api.constants; + +/** + * 邮件发送模块的常量 + * + * @author fengshuonan + * @date 2020/10/23 17:33 + */ +public interface MailConstants { + + /** + * 邮件模块的名称 + */ + String MAIL_MODULE_NAME = "kernel-d-email"; + + /** + * 异常枚举的步进值 + */ + String MAIL_EXCEPTION_STEP_CODE = "08"; + + /** + * 默认smtp的host + */ + String DEFAULT_SMTP_HOST = "smtp.126.com"; + + /** + * 默认smtp的端口 + */ + Integer DEFAULT_SMTP_PORT = 465; + + /** + * 默认邮件发送账号,这里不要修改,在系统配置表改 + */ + String DEFAULT_SMTP_USERNAME = "xxx@126.com"; + + /** + * 默认邮件发送密码或授权码,这里不要修改,在系统配置表改 + */ + String DEFAULT_SMTP_PASSWORD = "xxx"; + + /** + * 默认开启tls + */ + Boolean DEFAULT_SMTP_TLS_ENABLE = true; + + /** + * 是否开启账号密码验证 + */ + Boolean DEFAULT_SMTP_AUTH_ENABLE = true; + + /** + * 默认邮件的发送方 + */ + String DEFAULT_SMTP_SEND_FROM = DEFAULT_SMTP_USERNAME; + + /** + * 超时时间10秒 + */ + Long TIMEOUT_MILLISECOND = 10000L; + +} diff --git a/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/context/MailSenderContext.java b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/context/MailSenderContext.java new file mode 100644 index 000000000..00985de95 --- /dev/null +++ b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/context/MailSenderContext.java @@ -0,0 +1,24 @@ +package cn.stylefeng.roses.kernel.email.api.context; + +import cn.hutool.extra.spring.SpringUtil; +import cn.stylefeng.roses.kernel.email.api.MailSenderApi; + +/** + * 邮件发送的api上下文 + * + * @author fengshuonan + * @date 2020/10/26 10:16 + */ +public class MailSenderContext { + + /** + * 获取邮件发送的接口 + * + * @author fengshuonan + * @date 2020/10/26 10:16 + */ + public static MailSenderApi me() { + return SpringUtil.getBean(MailSenderApi.class); + } + +} diff --git a/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/exception/MailException.java b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/exception/MailException.java new file mode 100644 index 000000000..8c75feee9 --- /dev/null +++ b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/exception/MailException.java @@ -0,0 +1,29 @@ +package cn.stylefeng.roses.kernel.email.api.exception; + +import cn.stylefeng.roses.kernel.email.api.constants.MailConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; +import lombok.Getter; + +/** + * 邮件发送异常 + * + * @author fengshuonan + * @date 2018-07-06-下午3:00 + */ +@Getter +public class MailException extends ServiceException { + + public MailException(String errorCode, String userTip) { + super(MailConstants.MAIL_MODULE_NAME, errorCode, userTip); + } + + public MailException(AbstractExceptionEnum exceptionEnum, String userTip) { + super(MailConstants.MAIL_MODULE_NAME, exceptionEnum.getErrorCode(), userTip); + } + + public MailException(AbstractExceptionEnum exceptionEnum) { + super(MailConstants.MAIL_MODULE_NAME, exceptionEnum); + } + +} diff --git a/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/exception/enums/EmailExceptionEnum.java b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/exception/enums/EmailExceptionEnum.java new file mode 100644 index 000000000..ee954974a --- /dev/null +++ b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/exception/enums/EmailExceptionEnum.java @@ -0,0 +1,42 @@ +package cn.stylefeng.roses.kernel.email.api.exception.enums; + +import cn.stylefeng.roses.kernel.email.api.constants.MailConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import lombok.Getter; + +/** + * 邮件相关的异常 + * + * @author fengshuonan + * @date 2020/10/23 17:36 + */ +@Getter +public enum EmailExceptionEnum implements AbstractExceptionEnum { + + /** + * 邮件发送异常,请求参数存在空值 + */ + EMAIL_PARAM_EMPTY_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + MailConstants.MAIL_EXCEPTION_STEP_CODE + "01", "邮件发送失败,请检查参数配置,{}参数可能为空"), + + /** + * 阿里云邮件发送异常 + */ + ALIYUN_MAIL_SEND_ERROR(RuleConstants.THIRD_ERROR_TYPE_CODE + MailConstants.MAIL_EXCEPTION_STEP_CODE + "02", "阿里云邮件发送异常,errorCode:{}"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + EmailExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/expander/EmailConfigExpander.java b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/expander/EmailConfigExpander.java new file mode 100644 index 000000000..e27160c74 --- /dev/null +++ b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/expander/EmailConfigExpander.java @@ -0,0 +1,124 @@ +package cn.stylefeng.roses.kernel.email.api.expander; + +import cn.stylefeng.roses.kernel.config.api.context.ConfigContext; +import cn.stylefeng.roses.kernel.email.api.constants.MailConstants; + +/** + * 邮件相关的配置 + * + * @author fengshuonan + * @date 2020/12/1 11:45 + */ +public class EmailConfigExpander { + + /** + * smtp服务器地址,默认用126的邮箱 + * + * @author fengshuonan + * @date 2020/12/1 11:50 + */ + public static String getSmtpHost() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_EMAIL_SMTP_HOST", String.class, MailConstants.DEFAULT_SMTP_HOST); + } + + /** + * smtp服务端口 + * + * @author fengshuonan + * @date 2020/12/1 11:50 + */ + public static Integer getSmtpPort() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_EMAIL_SMTP_PORT", Integer.class, MailConstants.DEFAULT_SMTP_PORT); + } + + /** + * 是否启用账号密码验证 + * + * @author fengshuonan + * @date 2020/12/1 11:50 + */ + public static Boolean getSmtpAuthEnable() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_EMAIL_ENABLE_AUTH", Boolean.class, MailConstants.DEFAULT_SMTP_AUTH_ENABLE); + } + + /** + * 邮箱的账号 + * + * @author fengshuonan + * @date 2020/12/1 11:50 + */ + public static String getSmtpUser() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_EMAIL_ACCOUNT", String.class, MailConstants.DEFAULT_SMTP_USERNAME); + } + + /** + * 邮箱的密码或者授权码 + * + * @author fengshuonan + * @date 2020/12/1 11:50 + */ + public static String getSmtpPass() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_EMAIL_PASSWORD", String.class, MailConstants.DEFAULT_SMTP_PASSWORD); + } + + /** + * 邮箱的发送方邮箱 + * + * @author fengshuonan + * @date 2020/12/1 11:50 + */ + public static String getSmtpFrom() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_EMAIL_SEND_FROM", String.class, MailConstants.DEFAULT_SMTP_SEND_FROM); + } + + /** + * 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 + * + * @author fengshuonan + * @date 2020/12/1 11:50 + */ + public static Boolean getStartTlsEnable() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_EMAIL_START_TLS_ENABLE", Boolean.class, MailConstants.DEFAULT_SMTP_TLS_ENABLE); + } + + /** + * 使用 SSL安全连接 + * + * @author fengshuonan + * @date 2020/12/1 11:50 + */ + public static Boolean getSSLEnable() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_EMAIL_TLS_ENABLE", Boolean.class, MailConstants.DEFAULT_SMTP_TLS_ENABLE); + } + + /** + * 指定的端口连接到在使用指定的套接字工厂 + * + * @author fengshuonan + * @date 2020/12/1 11:50 + */ + public static Integer getSocketFactoryPort() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_EMAIL_SOCKET_FACTORY_PORT", Integer.class, MailConstants.DEFAULT_SMTP_PORT); + } + + /** + * SMTP超时时长,单位毫秒 + * + * @author fengshuonan + * @date 2020/12/1 11:50 + */ + public static Long getTimeout() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_EMAIL_SMTP_TIMEOUT", Long.class, MailConstants.TIMEOUT_MILLISECOND); + } + + /** + * Socket连接超时值,单位毫秒,缺省值不超时 + * + * @author fengshuonan + * @date 2020/12/1 11:50 + */ + public static Long getConnectionTimeout() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_EMAIL_CONNECTION_TIMEOUT", Long.class, MailConstants.TIMEOUT_MILLISECOND); + } + +} diff --git a/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/pojo/SendMailParam.java b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/pojo/SendMailParam.java new file mode 100644 index 000000000..57d937183 --- /dev/null +++ b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/pojo/SendMailParam.java @@ -0,0 +1,29 @@ +package cn.stylefeng.roses.kernel.email.api.pojo; + +import lombok.Data; + +/** + * 发送邮件的请求参数 + * + * @author fengshuonan + * @date 2018-07-05 21:19 + */ +@Data +public class SendMailParam { + + /** + * 发送给某人的邮箱 + */ + private String to; + + /** + * 邮件标题 + */ + private String title; + + /** + * 邮件内容 + */ + private String content; + +} diff --git a/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/pojo/aliyun/AliyunMailSenderProperties.java b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/pojo/aliyun/AliyunMailSenderProperties.java new file mode 100644 index 000000000..248ef7789 --- /dev/null +++ b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/pojo/aliyun/AliyunMailSenderProperties.java @@ -0,0 +1,29 @@ +package cn.stylefeng.roses.kernel.email.api.pojo.aliyun; + +import lombok.Data; + +/** + * 阿里云邮件发送的配置 + * + * @author fengshuonan + * @date 2020/10/30 22:35 + */ +@Data +public class AliyunMailSenderProperties { + + /** + * 发送邮件的key + */ + private String accessKeyId; + + /** + * 发送邮件的secret + */ + private String accessKeySecret; + + /** + * 发信人的地址,控制台配置 + */ + private String accountName; + +} diff --git a/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/pojo/aliyun/AliyunSendMailParam.java b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/pojo/aliyun/AliyunSendMailParam.java new file mode 100644 index 000000000..4de500bea --- /dev/null +++ b/kernel-d-email/email-api/src/main/java/cn/stylefeng/roses/kernel/email/api/pojo/aliyun/AliyunSendMailParam.java @@ -0,0 +1,51 @@ +package cn.stylefeng.roses.kernel.email.api.pojo.aliyun; + +import lombok.Data; + +/** + * 发送邮件的请求参数 + * + * @author fengshuonan + * @date 2018-07-05 21:19 + */ +@Data +public class AliyunSendMailParam { + + /** + * 发送给某人的邮箱 + *

+ * 发送给单个用户的邮箱时候用,只能写一个邮箱 + */ + private String to; + + /** + * 邮件标题 + */ + private String title; + + /** + * 邮件内容 + */ + private String content; + + /** + * 发信人昵称 + */ + private String fromAlias; + + /** + * 阿里云控制台,创建的标签名称 + */ + private String tagName; + + /** + * 预先创建且上传了收件人的收件人列表名称,在阿里云控制台创建 + */ + private String receiversName; + + /** + * 预先创建且通过审核的模板名称,在阿里云控制台创建 + */ + private String templateName; + +} diff --git a/kernel-d-email/email-sdk-aliyun/README.md b/kernel-d-email/email-sdk-aliyun/README.md new file mode 100644 index 000000000..423d5a9da --- /dev/null +++ b/kernel-d-email/email-sdk-aliyun/README.md @@ -0,0 +1 @@ +邮件发送模块 阿里云的实现 \ No newline at end of file diff --git a/kernel-d-email/email-sdk-aliyun/pom.xml b/kernel-d-email/email-sdk-aliyun/pom.xml new file mode 100644 index 000000000..45c85f81a --- /dev/null +++ b/kernel-d-email/email-sdk-aliyun/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-email + 1.0.0 + ../pom.xml + + + email-sdk-aliyun + + jar + + + + + + cn.stylefeng.roses + email-api + 1.0.0 + + + + + com.aliyun + aliyun-java-sdk-core + 3.0.0 + + + com.aliyun + aliyun-java-sdk-dm + 3.1.0 + + + + + diff --git a/kernel-d-email/email-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/email/aliyun/AliyunMailSender.java b/kernel-d-email/email-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/email/aliyun/AliyunMailSender.java new file mode 100644 index 000000000..9b58c1b05 --- /dev/null +++ b/kernel-d-email/email-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/email/aliyun/AliyunMailSender.java @@ -0,0 +1,224 @@ +package cn.stylefeng.roses.kernel.email.aliyun; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.dm.model.v20151123.BatchSendMailRequest; +import com.aliyuncs.dm.model.v20151123.SingleSendMailRequest; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.http.MethodType; +import com.aliyuncs.profile.DefaultProfile; +import com.aliyuncs.profile.IClientProfile; +import cn.stylefeng.roses.kernel.email.api.MailSenderApi; +import cn.stylefeng.roses.kernel.email.api.exception.MailException; +import cn.stylefeng.roses.kernel.email.api.exception.enums.EmailExceptionEnum; +import cn.stylefeng.roses.kernel.email.api.pojo.SendMailParam; +import cn.stylefeng.roses.kernel.email.api.pojo.aliyun.AliyunMailSenderProperties; +import cn.stylefeng.roses.kernel.email.api.pojo.aliyun.AliyunSendMailParam; + +import static cn.stylefeng.roses.kernel.email.api.exception.enums.EmailExceptionEnum.ALIYUN_MAIL_SEND_ERROR; + +/** + * 阿里云邮件发送的实现 + * + * @author fengshuonan + * @date 2020/10/30 22:26 + */ +public class AliyunMailSender implements MailSenderApi { + + private final AliyunMailSenderProperties aliyunMailSenderProperties; + + private IAcsClient acsClient; + + public AliyunMailSender(AliyunMailSenderProperties aliyunMailSenderProperties) { + this.aliyunMailSenderProperties = aliyunMailSenderProperties; + } + + @Override + public void sendMail(SendMailParam sendMailParam) { + } + + @Override + public void sendMailHtml(SendMailParam sendMailParam) { + } + + /** + * 发送单个阿里云邮件 + * + * @author fengshuonan + * @date 2020/10/30 23:39 + */ + public void sendAliyunMail(AliyunSendMailParam aliyunSendMailParam) { + + // 校验发送邮件的参数 + assertSendMailParams(aliyunSendMailParam); + + // 初始化客户端 + initClient(); + + // 创建发送单个邮件的请求 + SingleSendMailRequest singleSendRequest = createSingleSendRequest(aliyunSendMailParam); + + //如果调用成功,正常返回httpResponse;如果调用失败则抛出异常,需要在异常中捕获错误异常码;错误异常码请参考对应的API文档; + try { + acsClient.getAcsResponse(singleSendRequest); + } catch (ClientException e) { + String userTip = StrUtil.format(ALIYUN_MAIL_SEND_ERROR.getUserTip(), e.getErrCode()); + throw new MailException(ALIYUN_MAIL_SEND_ERROR, userTip); + } + } + + /** + * 批量发送阿里云邮件 + * + * @author fengshuonan + * @date 2020/10/30 23:39 + */ + public void sendBatchAliyunMail(AliyunSendMailParam aliyunSendMailParam) { + + // 校验发送邮件的参数 + assertSendMailParams(aliyunSendMailParam); + + // 初始化客户端 + initClient(); + + // 创建发送批量邮件的请求 + BatchSendMailRequest batchSendRequest = createBatchSendRequest(aliyunSendMailParam); + + //如果调用成功,正常返回httpResponse;如果调用失败则抛出异常,需要在异常中捕获错误异常码;错误异常码请参考对应的API文档; + try { + acsClient.getAcsResponse(batchSendRequest); + } catch (ClientException e) { + String userTip = StrUtil.format(ALIYUN_MAIL_SEND_ERROR.getUserTip(), e.getErrCode()); + throw new MailException(ALIYUN_MAIL_SEND_ERROR, userTip); + } + } + + /** + * 阿里云邮箱推送方式发送邮件 + * + * @author fengshuonan + * @date 2020/10/30 22:28 + */ + private IAcsClient initClient() { + if (acsClient != null) { + return acsClient; + } + + // 装载配置 + IClientProfile profile = DefaultProfile.getProfile( + "cn-hangzhou", + aliyunMailSenderProperties.getAccessKeyId(), + aliyunMailSenderProperties.getAccessKeySecret()); + + acsClient = new DefaultAcsClient(profile); + return acsClient; + } + + /** + * 创建发送一个邮件的请求 + * + * @author fengshuonan + * @date 2020/10/30 22:39 + */ + private SingleSendMailRequest createSingleSendRequest(AliyunSendMailParam aliyunSendMailParam) { + SingleSendMailRequest request = new SingleSendMailRequest(); + + // 控制台创建的发信地址 + request.setAccountName(aliyunMailSenderProperties.getAccountName()); + + // 发信人昵称 + request.setFromAlias(aliyunSendMailParam.getFromAlias()); + + // 地址类型:0-为随机账号,1-为发信地址 + request.setAddressType(1); + + // 控制台创建的标签 + request.setTagName(aliyunSendMailParam.getTagName()); + + // 使用管理台配置的回信地址 + request.setReplyToAddress(true); + + // 发信地址 + request.setToAddress(aliyunSendMailParam.getTo()); + + // 邮件主题 + request.setSubject(aliyunSendMailParam.getTitle()); + + //如果采用byte[].toString的方式的话请确保最终转换成utf-8的格式再放入htmlbody和textbody,若编码不一致则会被当成垃圾邮件。 + request.setHtmlBody(aliyunSendMailParam.getContent()); + + //SDK 采用的是http协议的发信方式, 默认是GET方法,有一定的长度限制。 + //若textBody、htmlBody或content的大小不确定,建议采用POST方式提交,避免出现uri is not valid异常 + request.setMethod(MethodType.POST); + + //是否开启追踪功能,开启需要备案,0关闭,1开启 + request.setClickTrace("0"); + + return request; + } + + /** + * 创建发送批量邮件的请求 + * + * @author fengshuonan + * @date 2020/10/30 22:39 + */ + private BatchSendMailRequest createBatchSendRequest(AliyunSendMailParam aliyunSendMailParam) { + BatchSendMailRequest request = new BatchSendMailRequest(); + + // 控制台创建的发信地址 + request.setAccountName(aliyunMailSenderProperties.getAccountName()); + + // 预先创建且上传了收件人的收件人列表名称 + request.setReceiversName(aliyunSendMailParam.getReceiversName()); + + // 邮件模板,在控制台创建,相当于邮件的内容 + request.setTemplateName(aliyunSendMailParam.getTemplateName()); + + // 地址类型:0-为随机账号,1-为发信地址 + request.setAddressType(1); + + // 控制台创建的标签 + request.setTagName(aliyunSendMailParam.getTagName()); + + //SDK 采用的是http协议的发信方式, 默认是GET方法,有一定的长度限制。 + //若textBody、htmlBody或content的大小不确定,建议采用POST方式提交,避免出现uri is not valid异常 + request.setMethod(MethodType.POST); + + //是否开启追踪功能,开启需要备案,0关闭,1开启 + request.setClickTrace("0"); + + return request; + } + + /** + * 校验发送邮件的请求参数 + * + * @author fengshuonan + * @date 2018/7/8 下午6:41 + */ + private void assertSendMailParams(AliyunSendMailParam aliyunSendMailParam) { + if (aliyunSendMailParam == null) { + String format = StrUtil.format(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getUserTip(), ""); + throw new MailException(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getErrorCode(), format); + } + + if (ObjectUtil.isEmpty(aliyunSendMailParam.getTo())) { + String format = StrUtil.format(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getUserTip(), "收件人邮箱"); + throw new MailException(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getErrorCode(), format); + } + + if (ObjectUtil.isEmpty(aliyunSendMailParam.getTitle())) { + String format = StrUtil.format(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getUserTip(), "邮件标题"); + throw new MailException(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getErrorCode(), format); + } + + if (ObjectUtil.isEmpty(aliyunSendMailParam.getContent())) { + String format = StrUtil.format(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getUserTip(), "邮件内容"); + throw new MailException(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getErrorCode(), format); + } + } + +} diff --git a/kernel-d-email/email-sdk-java/README.md b/kernel-d-email/email-sdk-java/README.md new file mode 100644 index 000000000..0442526e6 --- /dev/null +++ b/kernel-d-email/email-sdk-java/README.md @@ -0,0 +1 @@ +邮件发送的java原生实现 \ No newline at end of file diff --git a/kernel-d-email/email-sdk-java/pom.xml b/kernel-d-email/email-sdk-java/pom.xml new file mode 100644 index 000000000..e170ac99d --- /dev/null +++ b/kernel-d-email/email-sdk-java/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-email + 1.0.0 + ../pom.xml + + + email-sdk-java + + jar + + + + + + cn.stylefeng.roses + email-api + 1.0.0 + + + + + javax.mail + mail + 1.4.7 + + + + + diff --git a/kernel-d-email/email-sdk-java/src/main/java/cn/stylefeng/roses/kernel/email/JavaMailSender.java b/kernel-d-email/email-sdk-java/src/main/java/cn/stylefeng/roses/kernel/email/JavaMailSender.java new file mode 100644 index 000000000..0a6007e90 --- /dev/null +++ b/kernel-d-email/email-sdk-java/src/main/java/cn/stylefeng/roses/kernel/email/JavaMailSender.java @@ -0,0 +1,78 @@ +package cn.stylefeng.roses.kernel.email; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.mail.MailAccount; +import cn.hutool.extra.mail.MailUtil; +import cn.stylefeng.roses.kernel.email.api.MailSenderApi; +import cn.stylefeng.roses.kernel.email.api.exception.MailException; +import cn.stylefeng.roses.kernel.email.api.exception.enums.EmailExceptionEnum; +import cn.stylefeng.roses.kernel.email.api.pojo.SendMailParam; + +/** + * 邮件发送器 + * + * @author fengshuonan + * @date 2020/6/9 22:54 + */ +public class JavaMailSender implements MailSenderApi { + + /** + * 邮件配置 + */ + private final MailAccount mailAccount; + + public JavaMailSender(MailAccount mailAccount) { + this.mailAccount = mailAccount; + } + + @Override + public void sendMail(SendMailParam sendMailParam) { + + //校验发送邮件的参数 + assertSendMailParams(sendMailParam); + + //spring发送邮件 + MailUtil.send(mailAccount, CollUtil.newArrayList(sendMailParam.getTo()), sendMailParam.getTitle(), sendMailParam.getContent(), false); + } + + @Override + public void sendMailHtml(SendMailParam sendMailParam) { + + //校验发送邮件的参数 + assertSendMailParams(sendMailParam); + + //spring发送邮件 + MailUtil.send(mailAccount, CollUtil.newArrayList(sendMailParam.getTo()), sendMailParam.getTitle(), sendMailParam.getContent(), true); + } + + /** + * 校验发送邮件的请求参数 + * + * @author fengshuonan + * @date 2018/7/8 下午6:41 + */ + private void assertSendMailParams(SendMailParam sendMailParam) { + if (sendMailParam == null) { + String format = StrUtil.format(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getUserTip(), ""); + throw new MailException(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getErrorCode(), format); + } + + if (ObjectUtil.isEmpty(sendMailParam.getTo())) { + String format = StrUtil.format(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getUserTip(), "收件人邮箱"); + throw new MailException(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getErrorCode(), format); + } + + if (ObjectUtil.isEmpty(sendMailParam.getTitle())) { + String format = StrUtil.format(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getUserTip(), "邮件标题"); + throw new MailException(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getErrorCode(), format); + } + + if (ObjectUtil.isEmpty(sendMailParam.getContent())) { + String format = StrUtil.format(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getUserTip(), "邮件内容"); + throw new MailException(EmailExceptionEnum.EMAIL_PARAM_EMPTY_ERROR.getErrorCode(), format); + } + } + +} diff --git a/kernel-d-email/email-spring-boot-starter/README.md b/kernel-d-email/email-spring-boot-starter/README.md new file mode 100644 index 000000000..8db216b3e --- /dev/null +++ b/kernel-d-email/email-spring-boot-starter/README.md @@ -0,0 +1 @@ +邮件发送的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-email/email-spring-boot-starter/pom.xml b/kernel-d-email/email-spring-boot-starter/pom.xml new file mode 100644 index 000000000..f6dc7ad82 --- /dev/null +++ b/kernel-d-email/email-spring-boot-starter/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-email + 1.0.0 + ../pom.xml + + + email-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + email-sdk-java + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-email/email-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/email/starter/GunsEmailAutoConfiguration.java b/kernel-d-email/email-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/email/starter/GunsEmailAutoConfiguration.java new file mode 100644 index 000000000..f70dbdc04 --- /dev/null +++ b/kernel-d-email/email-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/email/starter/GunsEmailAutoConfiguration.java @@ -0,0 +1,47 @@ +package cn.stylefeng.roses.kernel.email.starter; + +import cn.hutool.extra.mail.MailAccount; +import cn.stylefeng.roses.kernel.email.JavaMailSender; +import cn.stylefeng.roses.kernel.email.api.MailSenderApi; +import cn.stylefeng.roses.kernel.email.api.expander.EmailConfigExpander; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 邮件发送的自动配置类 + * + * @author fengshuonan + * @date 2020/12/1 11:25 + */ +@Configuration +public class GunsEmailAutoConfiguration { + + /** + * java mail方式发送邮件的接口 + * + * @author fengshuonan + * @date 2020/12/1 11:32 + */ + @Bean + @ConditionalOnMissingBean(MailSenderApi.class) + public MailSenderApi mailSenderApi() { + MailAccount mailAccount = new MailAccount(); + + // 配置默认都从系统配置表获取 + mailAccount.setHost(EmailConfigExpander.getSmtpHost()); + mailAccount.setPort(EmailConfigExpander.getSmtpPort()); + mailAccount.setAuth(EmailConfigExpander.getSmtpAuthEnable()); + mailAccount.setUser(EmailConfigExpander.getSmtpUser()); + mailAccount.setPass(EmailConfigExpander.getSmtpPass()); + mailAccount.setFrom(EmailConfigExpander.getSmtpFrom()); + mailAccount.setStarttlsEnable(EmailConfigExpander.getStartTlsEnable()); + mailAccount.setSslEnable(EmailConfigExpander.getSSLEnable()); + mailAccount.setSocketFactoryPort(EmailConfigExpander.getSocketFactoryPort()); + mailAccount.setTimeout(EmailConfigExpander.getTimeout()); + mailAccount.setConnectionTimeout(EmailConfigExpander.getConnectionTimeout()); + + return new JavaMailSender(mailAccount); + } + +} diff --git a/kernel-d-email/email-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-email/email-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..e9f037fe3 --- /dev/null +++ b/kernel-d-email/email-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.email.starter.GunsEmailAutoConfiguration \ No newline at end of file diff --git a/kernel-d-email/pom.xml b/kernel-d-email/pom.xml new file mode 100644 index 000000000..593a459b4 --- /dev/null +++ b/kernel-d-email/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-email + + pom + + + email-api + email-sdk-java + email-sdk-aliyun + email-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-file/README.md b/kernel-d-file/README.md new file mode 100644 index 000000000..c2640152c --- /dev/null +++ b/kernel-d-file/README.md @@ -0,0 +1,3 @@ +文件模块 + +文件模块可实现阿里云,腾讯云,minio,与本地文件存储为介质的存储方式 \ No newline at end of file diff --git a/kernel-d-file/file-api/README.md b/kernel-d-file/file-api/README.md new file mode 100644 index 000000000..759a6decd --- /dev/null +++ b/kernel-d-file/file-api/README.md @@ -0,0 +1 @@ +文件管理api模块 \ No newline at end of file diff --git a/kernel-d-file/file-api/pom.xml b/kernel-d-file/file-api/pom.xml new file mode 100644 index 000000000..29400870d --- /dev/null +++ b/kernel-d-file/file-api/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-file + 1.0.0 + ../pom.xml + + + file-api + + jar + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + cn.stylefeng.roses + config-api + 1.0.0 + + + + + + javax.servlet + javax.servlet-api + provided + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/FileInfoApi.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/FileInfoApi.java new file mode 100644 index 000000000..312ac99c4 --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/FileInfoApi.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.file; + +import cn.stylefeng.roses.kernel.file.pojo.response.SysFileInfoResponse; + +/** + * 获取文件信息的api + * + * @author fengshuonan + * @date 2020/11/29 16:21 + */ +public interface FileInfoApi { + + /** + * 获取文件详情 + * + * @param fileId 文件id,在文件信息表的id + * @return 文件的信息,不包含文件本身的字节信息 + * @author fengshuonan + * @date 2020/11/29 16:26 + */ + SysFileInfoResponse getFileInfoWithoutContent(Long fileId); + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/FileOperatorApi.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/FileOperatorApi.java new file mode 100644 index 000000000..5d08e549e --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/FileOperatorApi.java @@ -0,0 +1,156 @@ +package cn.stylefeng.roses.kernel.file; + +import cn.stylefeng.roses.kernel.file.enums.BucketAuthEnum; + +import java.io.InputStream; + +/** + * 文件操纵者(内网操作) + *

+ * 如果存在未包含的操作,可以调用getClient()自行获取client进行操作 + * + * @author fengshuonan + * @date 2020/10/26 10:33 + */ +public interface FileOperatorApi { + + /** + * 初始化操作的客户端 + * + * @author fengshuonan + * @date 2020/10/26 10:33 + */ + void initClient(); + + /** + * 销毁操作的客户端 + * + * @author fengshuonan + * @date 2020/10/26 10:33 + */ + void destroyClient(); + + /** + * 获取操作的客户端 + *

+ * 例如,获取阿里云的客户端com.aliyun.oss.OSS + * + * @author fengshuonan + * @date 2020/10/26 10:35 + */ + Object getClient(); + + /** + * 查询存储桶是否存在 + *

+ * 例如:传入参数exampleBucket-1250000000,返回true代表存在此桶BucketAuthEnum.java + * + * @param bucketName 存储桶名称 + * @return boolean true-存在,false-不存在 + * @author fengshuonan + * @date 2020/10/26 10:36 + */ + boolean doesBucketExist(String bucketName); + + /** + * 设置预定义策略 + *

+ * 预定义策略如公有读、公有读写、私有读 + * + * @param bucketName 存储桶名称 + * @param bucketAuthEnum 存储桶的权限 + * @author fengshuonan + * @date 2020/10/26 10:37 + */ + void setBucketAcl(String bucketName, BucketAuthEnum bucketAuthEnum); + + /** + * 判断是否存在文件 + * + * @param bucketName 桶名称 + * @param key 唯一标示id,例如a.txt, doc/a.txt + * @return true-存在文件,false-不存在文件 + * @author fengshuonan + * @date 2020/10/26 10:38 + */ + boolean isExistingFile(String bucketName, String key); + + /** + * 存储文件 + * + * @param bucketName 桶名称 + * @param key 唯一标示id,例如a.txt, doc/a.txt + * @param bytes 文件字节数组 + * @author fengshuonan + * @date 2020/10/26 10:39 + */ + void storageFile(String bucketName, String key, byte[] bytes); + + /** + * 存储文件(存放到指定的bucket里边) + * + * @param bucketName 桶名称 + * @param key 唯一标示id,例如a.txt, doc/a.txt + * @param inputStream 文件流 + * @author fengshuonan + * @date 2020/10/26 10:39 + */ + void storageFile(String bucketName, String key, InputStream inputStream); + + /** + * 获取某个bucket下的文件字节 + * + * @param bucketName 桶名称 + * @param key 唯一标示id,例如a.txt, doc/a.txt + * @return byte[] 字节数组为文件的字节数组 + * @author fengshuonan + * @date 2020/10/26 10:39 + */ + byte[] getFileBytes(String bucketName, String key); + + /** + * 文件访问权限管理 + * + * @param bucketName 桶名称 + * @param key 唯一标示id,例如a.txt, doc/a.txt + * @param bucketAuthEnum 文件权限 + * @author fengshuonan + * @date 2020/10/26 10:40 + */ + void setFileAcl(String bucketName, String key, BucketAuthEnum bucketAuthEnum); + + /** + * 拷贝文件 + * + * @param originBucketName 源文件桶 + * @param originFileKey 源文件名称 + * @param newBucketName 新文件桶 + * @param newFileKey 新文件名称 + * @author fengshuonan + * @date 2020/10/26 10:40 + */ + void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey); + + /** + * 获取文件的下载地址(带鉴权的),生成外网地址 + * + * @param bucketName 文件桶 + * @param key 文件唯一标识 + * @param timeoutMillis url失效时间,单位毫秒 + * @return 外部系统可以直接访问的url + * @author fengshuonan + * @date 2020/10/26 10:40 + */ + String getFileAuthUrl(String bucketName, String key, Long timeoutMillis); + + /** + * 删除文件 + * + * @param bucketName 文件桶 + * @param key 文件唯一标识 + * @author fengshuonan + * @date 2020/10/26 10:42 + */ + void deleteFile(String bucketName, String key); + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/constants/FileConstants.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/constants/FileConstants.java new file mode 100644 index 000000000..cf0661bf1 --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/constants/FileConstants.java @@ -0,0 +1,46 @@ +package cn.stylefeng.roses.kernel.file.constants; + +/** + * 文件模块的常量 + * + * @author fengshuonan + * @date 2020/10/26 10:28 + */ +public interface FileConstants { + + /** + * 文件模块的名称 + */ + String FILE_MODULE_NAME = "kernel-d-file"; + + /** + * 异常枚举的步进值 + */ + String FILE_EXCEPTION_STEP_CODE = "09"; + + /** + * 文件分割符 + */ + String FILE_POSTFIX_SEPARATOR = "."; + + /** + * 默认文件存储的位置 + */ + String DEFAULT_BUCKET_NAME = "defaultBucket"; + + /** + * 服务默认部署的环境地质,不要改这个常量,改系统配置表中的配置 SYS_SERVER_DEPLOY_HOST + */ + String DEFAULT_SERVER_DEPLOY_HOST = "http://localhost"; + + /** + * 文件鉴权的时间,默认两小时 + */ + Long DEFAULT_FILE_TIMEOUT_SECONDS = 7200L; + + /** + * Guns中文件预览的接口 + */ + String FILE_PREVIEW_URL = "/sysFileInfo/preview"; + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/context/FileContext.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/context/FileContext.java new file mode 100644 index 000000000..b48f0f212 --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/context/FileContext.java @@ -0,0 +1,24 @@ +package cn.stylefeng.roses.kernel.file.context; + +import cn.hutool.extra.spring.SpringUtil; +import cn.stylefeng.roses.kernel.file.FileOperatorApi; + +/** + * 文件操作api的上下文 + * + * @author fengshuonan + * @date 2020/10/26 10:30 + */ +public class FileContext { + + /** + * 获取文件操作接口 + * + * @author fengshuonan + * @date 2020/10/17 14:30 + */ + public static FileOperatorApi me() { + return SpringUtil.getBean(FileOperatorApi.class); + } + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/enums/BucketAuthEnum.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/enums/BucketAuthEnum.java new file mode 100644 index 000000000..5880bdefa --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/enums/BucketAuthEnum.java @@ -0,0 +1,33 @@ +package cn.stylefeng.roses.kernel.file.enums; + +/** + * 桶的权限策略枚举 + * + * @author fengshuonan + * @date 2020-05-23-3:03 下午 + */ +public enum BucketAuthEnum { + + /** + * 私有的(仅有 owner 可以读写) + */ + PRIVATE, + + /** + * 公有读,私有写( owner 可以读写, 其他客户可以读) + */ + PUBLIC_READ, + + /** + * 公共读写(即所有人都可以读写,慎用) + */ + PUBLIC_READ_WRITE, + + /** + * 只写 (其他客户端只有写入文件权限,无读取文件权限) + *

+ * 即公有写,私有读( owner 可以读写, 其他客户可以写) + */ + MINIO_WRITE_ONLY, + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/enums/FileLocationEnum.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/enums/FileLocationEnum.java new file mode 100644 index 000000000..76a68790e --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/enums/FileLocationEnum.java @@ -0,0 +1,40 @@ +package cn.stylefeng.roses.kernel.file.enums; + +import lombok.Getter; + +/** + * 文件存储位置 + * + * @author stylefeng + * @date 2020/6/7 22:24 + */ +@Getter +public enum FileLocationEnum { + + /** + * 阿里云 + */ + ALIYUN(1), + + /** + * 腾讯云 + */ + TENCENT(2), + + /** + * minio服务器 + */ + MINIO(3), + + /** + * 本地 + */ + LOCAL(4); + + private final Integer code; + + FileLocationEnum(int code) { + this.code = code; + } + +} \ No newline at end of file diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/exception/FileException.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/exception/FileException.java new file mode 100644 index 000000000..b66a2a262 --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/exception/FileException.java @@ -0,0 +1,27 @@ +package cn.stylefeng.roses.kernel.file.exception; + +import cn.stylefeng.roses.kernel.file.constants.FileConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; + +/** + * 系统配置表的异常 + * + * @author fengshuonan + * @date 2020/10/15 15:59 + */ +public class FileException extends ServiceException { + + public FileException(String errorCode, String userTip) { + super(FileConstants.FILE_MODULE_NAME, errorCode, userTip); + } + + public FileException(AbstractExceptionEnum exception) { + super(FileConstants.FILE_MODULE_NAME, exception); + } + + public FileException(AbstractExceptionEnum exception, String userTip) { + super(FileConstants.FILE_MODULE_NAME, exception.getErrorCode(), userTip); + } + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/exception/enums/FileExceptionEnum.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/exception/enums/FileExceptionEnum.java new file mode 100644 index 000000000..12b2d7901 --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/exception/enums/FileExceptionEnum.java @@ -0,0 +1,87 @@ +package cn.stylefeng.roses.kernel.file.exception.enums; + +import cn.stylefeng.roses.kernel.file.constants.FileConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import lombok.Getter; + +/** + * 文件操作相关的异常枚举 + * + * @author fengshuonan + * @date 2020/10/26 11:29 + */ +@Getter +public enum FileExceptionEnum implements AbstractExceptionEnum { + + /** + * 阿里云文件操作异常 + */ + ALIYUN_FILE_ERROR(RuleConstants.THIRD_ERROR_TYPE_CODE + FileConstants.FILE_EXCEPTION_STEP_CODE + "01", "阿里云文件操作异常,具体信息为:{}"), + + /** + * 腾讯云文件操作异常 + */ + TENCENT_FILE_ERROR(RuleConstants.THIRD_ERROR_TYPE_CODE + FileConstants.FILE_EXCEPTION_STEP_CODE + "02", "腾讯云文件操作异常,具体信息为:{}"), + + /** + * 文件不存在 + */ + FILE_NOT_FOUND(RuleConstants.BUSINESS_ERROR_TYPE_CODE + FileConstants.FILE_EXCEPTION_STEP_CODE + "03", "本地文件不存在,具体信息为:{}"), + + /** + * MinIO文件操作异常 + */ + MINIO_FILE_ERROR(RuleConstants.THIRD_ERROR_TYPE_CODE + FileConstants.FILE_EXCEPTION_STEP_CODE + "04", "MinIO文件操作异常,具体信息为:{}"), + + /** + * 上传文件操作异常 + */ + ERROR_FILE(RuleConstants.BUSINESS_ERROR_TYPE_CODE + FileConstants.FILE_EXCEPTION_STEP_CODE + "05", "上传文件操作异常,具体信息为:{}"), + + /** + * 该条记录不存在 + */ + NOT_EXISTED(RuleConstants.BUSINESS_ERROR_TYPE_CODE + FileConstants.FILE_EXCEPTION_STEP_CODE + "06", "该文件信息不存在"), + + /** + * 获取文件流错误 + */ + FILE_STREAM_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + FileConstants.FILE_EXCEPTION_STEP_CODE + "07", "获取文件流错误"), + + /** + * 下载文件错误 + */ + DOWNLOAD_FILE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + FileConstants.FILE_EXCEPTION_STEP_CODE + "08", "下载文件错误,具体信息为:{}"), + + /** + * 预览文件异常 + */ + PREVIEW_ERROR_NOT_SUPPORT(RuleConstants.BUSINESS_ERROR_TYPE_CODE + FileConstants.FILE_EXCEPTION_STEP_CODE + "09", "预览文件异常,您预览的文件类型不支持或文件出现错误"), + + /** + * 预览文件参数存在空值 + */ + PREVIEW_EMPTY_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + FileConstants.FILE_EXCEPTION_STEP_CODE + "10", "预览文件参数存在空值,请求参数为:{}"), + + /** + * 渲染文件流字节出错 + */ + WRITE_BYTES_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + FileConstants.FILE_EXCEPTION_STEP_CODE + "10", "渲染文件流字节出错,具体信息为:{}"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + FileExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/expander/FileConfigExpander.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/expander/FileConfigExpander.java new file mode 100644 index 000000000..89e3ef5e7 --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/expander/FileConfigExpander.java @@ -0,0 +1,75 @@ +package cn.stylefeng.roses.kernel.file.expander; + +import cn.hutool.core.util.RandomUtil; +import cn.stylefeng.roses.kernel.config.api.context.ConfigContext; +import cn.stylefeng.roses.kernel.file.constants.FileConstants; +import cn.stylefeng.roses.kernel.file.pojo.props.LocalFileProperties; + +/** + * 文件相关的配置获取 + * + * @author fengshuonan + * @date 2020/11/29 14:47 + */ +public class FileConfigExpander { + + /** + * 获取服务部署的机器host,例如http://localhost + *

+ * 这个配置为了用在文件url的拼接上 + * + * @author fengshuonan + * @date 2020/11/29 16:13 + */ + public static String getServerDeployHost() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_SERVER_DEPLOY_HOST", String.class, FileConstants.DEFAULT_SERVER_DEPLOY_HOST); + } + + /** + * 获取文件生成auth url的失效时间 + * + * @author fengshuonan + * @date 2020/11/29 16:13 + */ + public static Long getDefaultFileTimeoutSeconds() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_DEFAULT_FILE_TIMEOUT_SECONDS", Long.class, FileConstants.DEFAULT_FILE_TIMEOUT_SECONDS); + } + + /** + * 用于专门给文件鉴权用的jwt的密钥,没配置的话会自动随机生成 + *

+ * 默认不写死,防止漏洞 + * + * @author fengshuonan + * @date 2020/11/29 16:13 + */ + public static String getFileAuthJwtSecret() { + String defaultFileTimeoutSeconds = ConfigContext.me().getConfigValueNullable("SYS_DEFAULT_FILE_TIMEOUT_SECONDS", String.class); + if (defaultFileTimeoutSeconds == null) { + return RandomUtil.randomString(20); + } else { + return defaultFileTimeoutSeconds; + } + } + + /** + * 本地文件存储位置(linux) + * + * @author fengshuonan + * @date 2020/12/1 14:44 + */ + public static String getLocalFileSavePathLinux() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_LOCAL_FILE_SAVE_PATH_LINUX", String.class, new LocalFileProperties().getLocalFileSavePathLinux()); + } + + /** + * 本地文件存储位置(windows) + * + * @author fengshuonan + * @date 2020/12/1 14:44 + */ + public static String getLocalFileSavePathWindows() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_LOCAL_FILE_SAVE_PATH_WINDOWS", String.class, new LocalFileProperties().getLocalFileSavePathWin()); + } + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/AliyunOssProperties.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/AliyunOssProperties.java new file mode 100644 index 000000000..2af60e555 --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/AliyunOssProperties.java @@ -0,0 +1,31 @@ +package cn.stylefeng.roses.kernel.file.pojo.props; + +import lombok.Data; + +/** + * 腾讯云cos文件存储配置 + * + * @author fengshuonan + * @date 2020/10/26 10:50 + */ +@Data +public class AliyunOssProperties { + + /** + * 默认北京,内网 + *

+ * https://help.aliyun.com/document_detail/31837.html?spm=a2c4g.11186623.2.17.467f45dcjB4WQQ#concept-zt4-cvy-5db + */ + private String endPoint = "http://oss-cn-beijing.aliyuncs.com"; + + /** + * 秘钥id + */ + private String accessKeyId; + + /** + * 秘钥secret + */ + private String accessKeySecret; + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/LocalFileProperties.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/LocalFileProperties.java new file mode 100644 index 000000000..410ea28d3 --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/LocalFileProperties.java @@ -0,0 +1,24 @@ +package cn.stylefeng.roses.kernel.file.pojo.props; + +import lombok.Data; + +/** + * 本地文件存储配置 + * + * @author fengshuonan + * @date 2020/6/7 22:30 + */ +@Data +public class LocalFileProperties { + + /** + * 本地文件存储位置(linux) + */ + private String localFileSavePathLinux = "/tmp/tempFilePath"; + + /** + * 本地文件存储位置(windows) + */ + private String localFileSavePathWin = "D:\\tempFilePath"; + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/MinIoProperties.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/MinIoProperties.java new file mode 100644 index 000000000..28627ece0 --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/MinIoProperties.java @@ -0,0 +1,29 @@ +package cn.stylefeng.roses.kernel.file.pojo.props; + +import lombok.Data; + +/** + * MinIO文件存储配置 + * + * @author luojie + * @date 2020/10/31 11:19 + */ +@Data +public class MinIoProperties { + + /** + * 服务器端点 MinIO服务器地址 默认:http://127.0.0.1:9000 + */ + private String endpoint = "http://127.0.0.1:9000"; + + /** + * MinIO accessKey + */ + private String accessKey; + + /** + * MinIO secretKey + */ + private String secretKey; + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/TenCosProperties.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/TenCosProperties.java new file mode 100644 index 000000000..6e8a87f14 --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/props/TenCosProperties.java @@ -0,0 +1,29 @@ +package cn.stylefeng.roses.kernel.file.pojo.props; + +import lombok.Data; + +/** + * 腾讯云cos文件存储配置 + * + * @author fengshuonan + * @date 2020/10/26 11:49 + */ +@Data +public class TenCosProperties { + + /** + * secretId + */ + private String secretId; + + /** + * secretKey + */ + private String secretKey; + + /** + * 地域id(默认北京) + */ + private String regionId = "ap-beijing"; + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/request/SysFileInfoRequest.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/request/SysFileInfoRequest.java new file mode 100644 index 000000000..9dd187654 --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/request/SysFileInfoRequest.java @@ -0,0 +1,68 @@ +package cn.stylefeng.roses.kernel.file.pojo.request; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotNull; + +/** + *

+ * 文件信息表 + *

+ * + * @author stylefeng + * @date 2020/6/7 22:15 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysFileInfoRequest extends BaseRequest { + + /** + * 主键id + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {delete.class, detail.class}) + private Long id; + + /** + * 文件存储位置(1:阿里云,2:腾讯云,3:minio,4:本地) + */ + private Integer fileLocation; + + /** + * 文件仓库 + */ + private String fileBucket; + + /** + * 文件名称(上传时候的文件名) + */ + private String fileOriginName; + + /** + * 文件后缀 + */ + private String fileSuffix; + + /** + * 文件大小kb + */ + private Long fileSizeKb; + + /** + * 存储到bucket的名称(文件唯一标识id) + */ + private String fileObjectName; + + /** + * 存储路径 + */ + private String filePath; + + /** + * 校验组,预览图片 + */ + public @interface preview { + } + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/response/SysFileInfoResponse.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/response/SysFileInfoResponse.java new file mode 100644 index 000000000..caac913d1 --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/pojo/response/SysFileInfoResponse.java @@ -0,0 +1,59 @@ +package cn.stylefeng.roses.kernel.file.pojo.response; + +import lombok.Data; + +/** + * 文件信息结果集 + * + * @author stylefeng + * @date 2020/6/7 22:15 + */ +@Data +public class SysFileInfoResponse { + + /** + * 主键id + */ + private Long id; + + /** + * 文件存储位置(1:阿里云,2:腾讯云,3:minio,4:本地) + */ + private Integer fileLocation; + + /** + * 文件仓库 + */ + private String fileBucket; + + /** + * 文件名称(上传时候的文件名) + */ + private String fileOriginName; + + /** + * 文件后缀 + */ + private String fileSuffix; + + /** + * 文件大小kb + */ + private Long fileSizeKb; + + /** + * 存储到bucket的名称(文件唯一标识id) + */ + private String fileObjectName; + + /** + * 存储路径 + */ + private String filePath; + + /** + * 文件的字节 + */ + private byte[] fileBytes; + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/util/DownloadUtil.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/util/DownloadUtil.java new file mode 100644 index 000000000..a93d191ec --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/util/DownloadUtil.java @@ -0,0 +1,76 @@ +package cn.stylefeng.roses.kernel.file.util; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.stylefeng.roses.kernel.file.exception.FileException; +import cn.stylefeng.roses.kernel.file.exception.enums.FileExceptionEnum; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +/** + * web文件下载工具封装 + * + * @author fengshuonan + * @date 2020/11/29 11:36 + */ +@Slf4j +public class DownloadUtil { + + /** + * 根据文件名和文件的字节数组下载文件 + * + * @param fileName 文件真实名称,最终返回给用户的 + * @param fileBytes 文件的真实字节数组 + * @param response servlet response对象 + * @author fengshuonan + * @date 2020/11/29 13:52 + */ + public static void download(String fileName, byte[] fileBytes, HttpServletResponse response) { + try { + response.reset(); + response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); + response.setHeader("Content-Disposition", "attachment; filename=\"" + URLUtil.encode(fileName) + "\""); + response.addHeader("Content-Length", "" + fileBytes.length); + response.setContentType("application/octet-stream;charset=UTF-8"); + IoUtil.write(response.getOutputStream(), true, fileBytes); + } catch (IOException e) { + String userTip = StrUtil.format(FileExceptionEnum.DOWNLOAD_FILE_ERROR.getUserTip(), e.getMessage()); + throw new FileException(FileExceptionEnum.DOWNLOAD_FILE_ERROR, userTip); + } + } + + /** + * 下载文件 + * + * @param file 要下载的文件 + * @param response 响应 + * @author fengshuonan + * @date 2020/11/29 13:53 + */ + public static void download(File file, HttpServletResponse response) { + + // 获取文件字节 + byte[] fileBytes = FileUtil.readBytes(file); + + //获取文件名称 + String fileName; + try { + fileName = URLEncoder.encode(file.getName(), CharsetUtil.UTF_8); + } catch (UnsupportedEncodingException e) { + String userTip = StrUtil.format(FileExceptionEnum.DOWNLOAD_FILE_ERROR.getUserTip(), e.getMessage()); + throw new FileException(FileExceptionEnum.DOWNLOAD_FILE_ERROR, userTip); + } + + //下载文件 + download(fileName, fileBytes, response); + } + +} diff --git a/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/util/PicFileTypeUtil.java b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/util/PicFileTypeUtil.java new file mode 100644 index 000000000..fad8a0535 --- /dev/null +++ b/kernel-d-file/file-api/src/main/java/cn/stylefeng/roses/kernel/file/util/PicFileTypeUtil.java @@ -0,0 +1,50 @@ +package cn.stylefeng.roses.kernel.file.util; + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.List; + +/** + * 文件类型识别工具 + * + * @author fengshuonan + * @date 2020/11/29 14:00 + */ +public class PicFileTypeUtil { + + private static final List PIC_TYPES; + + static { + PIC_TYPES = new ArrayList<>(); + PIC_TYPES.add("jpg"); + PIC_TYPES.add("png"); + PIC_TYPES.add("jpeg"); + PIC_TYPES.add("tif"); + PIC_TYPES.add("gif"); + PIC_TYPES.add("bmp"); + } + + /** + * 根据文件名称获取文件是否为图片类型 + * + * @param fileName 文件名称 + * @return boolean true-是图片类型,false-不是图片类型 + * @author fengshuonan + * @date 2020/11/29 14:04 + */ + public static boolean getFileImgTypeFlag(String fileName) { + if (StrUtil.isEmpty(fileName)) { + return false; + } + + for (String picType : PIC_TYPES) { + if (fileName.toLowerCase().endsWith(picType)) { + return true; + } + } + + return false; + } + +} diff --git a/kernel-d-file/file-business/README.md b/kernel-d-file/file-business/README.md new file mode 100644 index 000000000..428362f18 --- /dev/null +++ b/kernel-d-file/file-business/README.md @@ -0,0 +1 @@ +文件管理模块,可以实现,在线管理和维护文件内容 \ No newline at end of file diff --git a/kernel-d-file/file-business/pom.xml b/kernel-d-file/file-business/pom.xml new file mode 100644 index 000000000..0aa461952 --- /dev/null +++ b/kernel-d-file/file-business/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-file + 1.0.0 + ../pom.xml + + + file-business + + jar + + + + + + cn.stylefeng.roses + file-api + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/controller/SysFileInfoController.java b/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/controller/SysFileInfoController.java new file mode 100644 index 000000000..856fb007a --- /dev/null +++ b/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/controller/SysFileInfoController.java @@ -0,0 +1,139 @@ +package cn.stylefeng.roses.kernel.file.modular.controller; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.file.pojo.request.SysFileInfoRequest; +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.file.exception.FileException; +import cn.stylefeng.roses.kernel.file.exception.enums.FileExceptionEnum; +import cn.stylefeng.roses.kernel.file.modular.service.SysFileInfoService; +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; + +import static cn.stylefeng.roses.kernel.file.constants.FileConstants.FILE_PREVIEW_URL; + +/** + * 文件信息管理 + * + * @author fengshuonan + * @date 2020/11/29 11:17 + */ +@RestController +@ApiResource(name = "文件信息相关接口") +public class SysFileInfoController { + + @Resource + private SysFileInfoService sysFileInfoService; + + /** + * 上传文件 + * + * @author fengshuonan + * @date 2020/11/29 11:17 + */ + @PostResource(name = "上传文件", path = "/sysFileInfo/upload") + public ResponseData upload(@RequestPart("file") MultipartFile file) { + Long fileId = sysFileInfoService.uploadFile(file); + return new SuccessResponseData(fileId); + } + + /** + * 下载文件 + * + * @author fengshuonan + * @date 2020/11/29 11:29 + */ + @GetResource(name = "下载文件", path = "/sysFileInfo/download") + public void download(@Validated(BaseRequest.detail.class) SysFileInfoRequest sysFileInfoRequest, HttpServletResponse response) { + sysFileInfoService.download(sysFileInfoRequest, response); + } + + /** + * 删除文件信息 + * + * @author fengshuonan + * @date 2020/11/29 11:19 + */ + @PostResource(name = "删除文件信息", path = "/sysFileInfo/delete") + public ResponseData delete(@RequestBody @Validated(SysFileInfoRequest.delete.class) SysFileInfoRequest sysFileInfoRequest) { + sysFileInfoService.delete(sysFileInfoRequest); + return new SuccessResponseData(); + } + + /** + * 文件预览 + *

+ * 支持通过文件id预览,也支持直接通过bucket和obj名称预览 + * + * @author fengshuonan + * @date 2020/11/29 11:29 + */ + @GetResource(name = "文件预览", path = FILE_PREVIEW_URL) + public void preview(SysFileInfoRequest sysFileInfoRequest, HttpServletResponse response) { + + // 请求参数不能为空 + if (sysFileInfoRequest == null) { + String userTip = StrUtil.format(FileExceptionEnum.PREVIEW_EMPTY_ERROR.getUserTip(), ""); + throw new FileException(FileExceptionEnum.PREVIEW_EMPTY_ERROR, userTip); + } + + // 文件id不为空,则根据文件id预览 + if (ObjectUtil.isNotEmpty(sysFileInfoRequest.getId())) { + sysFileInfoService.previewByFileId(sysFileInfoRequest, response); + } + + // 文件bucketName和objectName不为空,则根据bucket预览 + if (ObjectUtil.isAllNotEmpty(sysFileInfoRequest.getFileBucket(), sysFileInfoRequest.getFileObjectName())) { + sysFileInfoService.previewByBucketAndObjName(sysFileInfoRequest, response); + } + + // 提示用户信息不全 + String userTip = StrUtil.format(FileExceptionEnum.PREVIEW_EMPTY_ERROR.getUserTip(), sysFileInfoRequest); + throw new FileException(FileExceptionEnum.PREVIEW_EMPTY_ERROR, userTip); + } + + /** + * 查看详情文件信息表 + * + * @author fengshuonan + * @date 2020/11/29 11:29 + */ + @GetResource(name = "查看详情文件信息表", path = "/sysFileInfo/detail") + public ResponseData detail(@Validated(SysFileInfoRequest.detail.class) SysFileInfoRequest sysFileInfoRequest) { + return new SuccessResponseData(sysFileInfoService.detail(sysFileInfoRequest)); + } + + /** + * 分页查询文件信息表 + * + * @author fengshuonan + * @date 2020/11/29 11:29 + */ + @GetResource(name = "分页查询文件信息表", path = "/sysFileInfo/page") + public ResponseData page(SysFileInfoRequest sysFileInfoRequest) { + return new SuccessResponseData(sysFileInfoService.page(sysFileInfoRequest)); + } + + /** + * 获取全部文件信息表 + * + * @author fengshuonan + * @date 2020/11/29 11:29 + */ + @GetResource(name = "获取全部文件信息表", path = "/sysFileInfo/list") + public ResponseData list(SysFileInfoRequest sysFileInfoRequest) { + return new SuccessResponseData(sysFileInfoService.list(sysFileInfoRequest)); + } + +} \ No newline at end of file diff --git a/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/entity/SysFileInfo.java b/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/entity/SysFileInfo.java new file mode 100644 index 000000000..0936fdd8a --- /dev/null +++ b/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/entity/SysFileInfo.java @@ -0,0 +1,78 @@ +package cn.stylefeng.roses.kernel.file.modular.entity; + +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 cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 文件信息表 + *

+ * + * @author stylefeng + * @date 2020/6/7 22:15 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_file_info") +public class SysFileInfo extends BaseEntity { + + /** + * 主键id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 文件存储位置(1:阿里云,2:腾讯云,3:minio,4:本地) + */ + @TableField("file_location") + private Integer fileLocation; + + /** + * 文件仓库 + */ + @TableField("file_bucket") + private String fileBucket; + + /** + * 文件名称(上传时候的文件名) + */ + @TableField("file_origin_name") + private String fileOriginName; + + /** + * 文件后缀 + */ + @TableField("file_suffix") + private String fileSuffix; + + /** + * 文件大小kb + */ + @TableField("file_size_kb") + private Long fileSizeKb; + + /** + * 文件大小信息,计算后的 + */ + @TableField("file_size_info") + private String fileSizeInfo; + + /** + * 存储到bucket的名称(文件唯一标识id) + */ + @TableField("file_object_name") + private String fileObjectName; + + /** + * 存储路径 + */ + @TableField("file_path") + private String filePath; + +} diff --git a/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/mapper/SysFileInfoMapper.java b/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/mapper/SysFileInfoMapper.java new file mode 100644 index 000000000..ee1c04b83 --- /dev/null +++ b/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/mapper/SysFileInfoMapper.java @@ -0,0 +1,16 @@ +package cn.stylefeng.roses.kernel.file.modular.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.stylefeng.roses.kernel.file.modular.entity.SysFileInfo; + +/** + *

+ * 文件信息表 Mapper 接口 + *

+ * + * @author stylefeng + * @date 2020/6/7 22:15 + */ +public interface SysFileInfoMapper extends BaseMapper { + +} diff --git a/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/mapper/mapping/SysFileInfoMapper.xml b/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/mapper/mapping/SysFileInfoMapper.xml new file mode 100644 index 000000000..69bde3de9 --- /dev/null +++ b/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/mapper/mapping/SysFileInfoMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/service/SysFileInfoService.java b/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/service/SysFileInfoService.java new file mode 100644 index 000000000..04822101e --- /dev/null +++ b/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/service/SysFileInfoService.java @@ -0,0 +1,110 @@ +package cn.stylefeng.roses.kernel.file.modular.service; + +import cn.stylefeng.roses.kernel.file.pojo.request.SysFileInfoRequest; +import cn.stylefeng.roses.kernel.file.pojo.response.SysFileInfoResponse; +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.file.modular.entity.SysFileInfo; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 文件信息表 服务类 + * + * @author stylefeng + * @date 2020/6/7 22:15 + */ +public interface SysFileInfoService extends IService { + + /** + * 上传文件,返回文件的唯一标识 + * + * @param file 要上传的文件 + * @return 文件id + * @author fengshuonan + * @date 2020/11/29 13:38 + */ + Long uploadFile(MultipartFile file); + + /** + * 文件下载 + * + * @param sysFileInfoRequest 文件下载参数 + * @param response 响应结果 + * @author fengshuonan + * @date 2020/11/29 13:39 + */ + void download(SysFileInfoRequest sysFileInfoRequest, HttpServletResponse response); + + /** + * 删除文件信息表 + * + * @param sysFileInfoRequest 删除参数 + * @author fengshuonan + * @date 2020/11/29 13:44 + */ + void delete(SysFileInfoRequest sysFileInfoRequest); + + /** + * 文件预览,通过参数中传递文件id + * + * @param sysFileInfoRequest 文件预览参数 + * @param response 响应结果 + * @author fengshuonan + * @date 2020/11/29 13:45 + */ + void previewByFileId(SysFileInfoRequest sysFileInfoRequest, HttpServletResponse response); + + /** + * 文件预览,通过参数中传递fileBucket和fileObjectName + * + * @param sysFileInfoRequest 文件预览参数 + * @param response 响应结果 + * @author fengshuonan + * @date 2020/11/29 13:45 + */ + void previewByBucketAndObjName(SysFileInfoRequest sysFileInfoRequest, HttpServletResponse response); + + /** + * 查看详情文件信息表 + * + * @param sysFileInfoRequest 查看参数 + * @return 文件信息 + * @author fengshuonan + * @date 2020/11/29 14:08 + */ + SysFileInfo detail(SysFileInfoRequest sysFileInfoRequest); + + /** + * 分页查询文件信息表 + * + * @param sysFileInfoRequest 查询参数 + * @return 查询分页结果 + * @author fengshuonan + * @date 2020/11/29 14:09 + */ + PageResult page(SysFileInfoRequest sysFileInfoRequest); + + /** + * 查询所有文件信息表 + * + * @param sysFileInfoRequest 查询参数 + * @return 文件信息列表 + * @author fengshuonan + * @date 2020/11/29 14:15 + */ + List list(SysFileInfoRequest sysFileInfoRequest); + + /** + * 获取文件信息结果集 + * + * @param fileId 文件id + * @return 文件信息结果集 + * @author fengshuonan + * @date 2020/11/29 14:16 + */ + SysFileInfoResponse getFileInfoResult(Long fileId); + +} diff --git a/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/service/impl/SysFileInfoServiceImpl.java b/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/service/impl/SysFileInfoServiceImpl.java new file mode 100644 index 000000000..666a771c3 --- /dev/null +++ b/kernel-d-file/file-business/src/main/java/cn/stylefeng/roses/kernel/file/modular/service/impl/SysFileInfoServiceImpl.java @@ -0,0 +1,306 @@ +package cn.stylefeng.roses.kernel.file.modular.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.file.pojo.request.SysFileInfoRequest; +import cn.stylefeng.roses.kernel.file.pojo.response.SysFileInfoResponse; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.file.FileInfoApi; +import cn.stylefeng.roses.kernel.file.FileOperatorApi; +import cn.stylefeng.roses.kernel.file.enums.FileLocationEnum; +import cn.stylefeng.roses.kernel.file.exception.FileException; +import cn.stylefeng.roses.kernel.file.exception.enums.FileExceptionEnum; +import cn.stylefeng.roses.kernel.file.modular.entity.SysFileInfo; +import cn.stylefeng.roses.kernel.file.modular.mapper.SysFileInfoMapper; +import cn.stylefeng.roses.kernel.file.modular.service.SysFileInfoService; +import cn.stylefeng.roses.kernel.file.util.DownloadUtil; +import cn.stylefeng.roses.kernel.file.util.PicFileTypeUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.List; + +import static cn.stylefeng.roses.kernel.file.constants.FileConstants.DEFAULT_BUCKET_NAME; +import static cn.stylefeng.roses.kernel.file.constants.FileConstants.FILE_POSTFIX_SEPARATOR; + +/** + * 文件信息表 服务实现类 + * + * @author stylefeng + * @date 2020/6/7 22:15 + */ +@Service +@Slf4j +public class SysFileInfoServiceImpl extends ServiceImpl implements SysFileInfoService, FileInfoApi { + + @Resource + private FileOperatorApi fileOperatorApi; + + @Override + public Long uploadFile(MultipartFile file) { + + // 生成文件的唯一id + Long fileId = IdWorker.getId(); + + // 获取文件原始名称 + String originalFilename = file.getOriginalFilename(); + + // 获取文件后缀,不包含点 + String fileSuffix = null; + if (ObjectUtil.isNotEmpty(originalFilename)) { + fileSuffix = StrUtil.subAfter(originalFilename, FILE_POSTFIX_SEPARATOR, true); + } + + // 生成文件的最终名称 + String finalFileName = fileId + FILE_POSTFIX_SEPARATOR + fileSuffix; + + // 存储文件 + byte[] bytes; + try { + bytes = file.getBytes(); + fileOperatorApi.storageFile(DEFAULT_BUCKET_NAME, finalFileName, bytes); + } catch (IOException e) { + String userTip = StrUtil.format(FileExceptionEnum.ERROR_FILE.getUserTip(), e.getMessage()); + throw new FileException(FileExceptionEnum.ERROR_FILE, userTip); + } + + // 计算文件大小kb + long fileSizeKb = Convert.toLong(NumberUtil.div(new BigDecimal(file.getSize()), BigDecimal.valueOf(1024)) + .setScale(0, BigDecimal.ROUND_HALF_UP)); + + //计算文件大小信息 + String fileSizeInfo = FileUtil.readableFileSize(file.getSize()); + + // 存储文件信息 + SysFileInfo sysFileInfo = new SysFileInfo(); + sysFileInfo.setId(fileId); + sysFileInfo.setFileLocation(FileLocationEnum.LOCAL.getCode()); + sysFileInfo.setFileBucket(DEFAULT_BUCKET_NAME); + sysFileInfo.setFileObjectName(finalFileName); + sysFileInfo.setFileOriginName(originalFilename); + sysFileInfo.setFileSuffix(fileSuffix); + sysFileInfo.setFileSizeKb(fileSizeKb); + sysFileInfo.setFileSizeInfo(fileSizeInfo); + this.save(sysFileInfo); + + // 返回文件id + return fileId; + } + + @Override + public void download(SysFileInfoRequest sysFileInfoRequest, HttpServletResponse response) { + SysFileInfoResponse sysFileInfoResult = this.getFileInfoResult(sysFileInfoRequest.getId()); + String fileName = sysFileInfoResult.getFileOriginName(); + byte[] fileBytes = sysFileInfoResult.getFileBytes(); + DownloadUtil.download(fileName, fileBytes, response); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(SysFileInfoRequest sysFileInfoRequest) { + + // 查询文件的信息 + SysFileInfo sysFileInfo = this.getById(sysFileInfoRequest.getId()); + + // 删除文件记录 + this.removeById(sysFileInfoRequest.getId()); + + // 删除具体文件 + this.fileOperatorApi.deleteFile(sysFileInfo.getFileBucket(), sysFileInfo.getFileObjectName()); + } + + @Override + public void previewByFileId(SysFileInfoRequest sysFileInfoRequest, HttpServletResponse response) { + + byte[] fileBytes; + + // 根据文件id获取文件信息结果集 + SysFileInfoResponse sysFileInfoResult = this.getFileInfoResult(sysFileInfoRequest.getId()); + + // 获取文件后缀 + String fileSuffix = sysFileInfoResult.getFileSuffix().toLowerCase(); + + // 获取文件字节码 + fileBytes = sysFileInfoResult.getFileBytes(); + + // 如果是图片类型,则直接输出 + if (PicFileTypeUtil.getFileImgTypeFlag(fileSuffix)) { + renderPreviewFile(response, fileBytes); + } else { + // 不支持别的文件预览 + throw new FileException(FileExceptionEnum.PREVIEW_ERROR_NOT_SUPPORT); + } + } + + @Override + public void previewByBucketAndObjName(SysFileInfoRequest sysFileInfoRequest, HttpServletResponse response) { + + // 获取文件字节码 + byte[] fileBytes; + try { + fileBytes = fileOperatorApi.getFileBytes(sysFileInfoRequest.getFileBucket(), sysFileInfoRequest.getFileObjectName()); + } catch (Exception e) { + log.error(">>> 获取文件流异常,具体信息为:{}", e.getMessage()); + throw new FileException(FileExceptionEnum.FILE_STREAM_ERROR); + } + + // 如果是图片类型,则直接输出 + if (PicFileTypeUtil.getFileImgTypeFlag(sysFileInfoRequest.getFileObjectName())) { + renderPreviewFile(response, fileBytes); + } else { + // 不支持别的文件预览 + throw new FileException(FileExceptionEnum.PREVIEW_ERROR_NOT_SUPPORT); + } + } + + @Override + public SysFileInfo detail(SysFileInfoRequest sysFileInfoRequest) { + return this.querySysFileInfo(sysFileInfoRequest); + } + + @Override + public PageResult page(SysFileInfoRequest sysFileInfoRequest) { + LambdaQueryWrapper queryWrapper = createWrapper(sysFileInfoRequest); + Page page = this.page(PageFactory.defaultPage(), queryWrapper); + return PageResultFactory.createPageResult(page); + } + + @Override + public List list(SysFileInfoRequest sysFileInfoRequest) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + return this.list(queryWrapper); + } + + @Override + public SysFileInfoResponse getFileInfoResult(Long fileId) { + + SysFileInfoResponse sysFileInfoResult = new SysFileInfoResponse(); + + // 查询库中文件信息 + SysFileInfoRequest sysFileInfoRequest = new SysFileInfoRequest(); + sysFileInfoRequest.setId(fileId); + SysFileInfo sysFileInfo = this.querySysFileInfo(sysFileInfoRequest); + + // 获取文件字节码 + byte[] fileBytes; + try { + fileBytes = fileOperatorApi.getFileBytes(DEFAULT_BUCKET_NAME, sysFileInfo.getFileObjectName()); + } catch (Exception e) { + log.error(">>> 获取文件流异常,具体信息为:{}", e.getMessage()); + throw new FileException(FileExceptionEnum.FILE_STREAM_ERROR); + } + + // 设置文件字节码 + BeanUtil.copyProperties(sysFileInfo, sysFileInfoResult); + sysFileInfoResult.setFileBytes(fileBytes); + + return sysFileInfoResult; + } + + @Override + public SysFileInfoResponse getFileInfoWithoutContent(Long fileId) { + + SysFileInfoRequest sysFileInfoRequest = new SysFileInfoRequest(); + sysFileInfoRequest.setId(fileId); + + // 获取文件的基本信息 + SysFileInfo sysFileInfo = querySysFileInfo(sysFileInfoRequest); + + // 转化实体 + SysFileInfoResponse sysFileInfoResponse = new SysFileInfoResponse(); + BeanUtil.copyProperties(sysFileInfo, sysFileInfoResponse); + + return sysFileInfoResponse; + } + + /** + * 获取文件信息表 + * + * @author fengshuonan + * @date 2020/11/29 13:40 + */ + private SysFileInfo querySysFileInfo(SysFileInfoRequest sysFileInfoRequest) { + SysFileInfo sysFileInfo = this.getById(sysFileInfoRequest.getId()); + if (ObjectUtil.isEmpty(sysFileInfo)) { + throw new FileException(FileExceptionEnum.NOT_EXISTED); + } + return sysFileInfo; + } + + /** + * 创建组织架构的通用条件查询wrapper + * + * @author fengshuonan + * @date 2020/11/6 10:16 + */ + private LambdaQueryWrapper createWrapper(SysFileInfoRequest sysFileInfoRequest) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysFileInfoRequest)) { + + // 拼接文件存储位置条件 + if (ObjectUtil.isNotEmpty(sysFileInfoRequest.getFileLocation())) { + queryWrapper.like(SysFileInfo::getFileLocation, sysFileInfoRequest.getFileLocation()); + } + + // 拼接文件仓库条件 + if (ObjectUtil.isNotEmpty(sysFileInfoRequest.getFileBucket())) { + queryWrapper.like(SysFileInfo::getFileBucket, sysFileInfoRequest.getFileBucket()); + } + + // 拼接文件名称(上传时候的文件名) + if (ObjectUtil.isNotEmpty(sysFileInfoRequest.getFileOriginName())) { + queryWrapper.like(SysFileInfo::getFileOriginName, sysFileInfoRequest.getFileOriginName()); + } + + // 拼接文件名称(存储到系统的文件名) + if (ObjectUtil.isNotEmpty(sysFileInfoRequest.getFileObjectName())) { + queryWrapper.like(SysFileInfo::getFileObjectName, sysFileInfoRequest.getFileObjectName()); + } + + } + + return queryWrapper; + } + + /** + * 渲染被预览的文件到servlet的response流中 + * + * @author fengshuonan + * @date 2020/11/29 17:13 + */ + private void renderPreviewFile(HttpServletResponse response, byte[] fileBytes) { + try { + // 设置contentType + response.setContentType(MediaType.IMAGE_PNG_VALUE); + + // 获取outputStream + ServletOutputStream outputStream = response.getOutputStream(); + + // 输出字节流 + IoUtil.write(outputStream, true, fileBytes); + } catch (IOException e) { + String userTip = StrUtil.format(FileExceptionEnum.WRITE_BYTES_ERROR.getUserTip(), e.getMessage()); + throw new FileException(FileExceptionEnum.WRITE_BYTES_ERROR, userTip); + } + } + +} diff --git a/kernel-d-file/file-sdk-aliyun/README.md b/kernel-d-file/file-sdk-aliyun/README.md new file mode 100644 index 000000000..9ef82a3ad --- /dev/null +++ b/kernel-d-file/file-sdk-aliyun/README.md @@ -0,0 +1 @@ +文件模块,阿里云文件的实现 \ No newline at end of file diff --git a/kernel-d-file/file-sdk-aliyun/pom.xml b/kernel-d-file/file-sdk-aliyun/pom.xml new file mode 100644 index 000000000..d5360625d --- /dev/null +++ b/kernel-d-file/file-sdk-aliyun/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-file + 1.0.0 + ../pom.xml + + + file-sdk-aliyun + + jar + + + + + + cn.stylefeng.roses + file-api + 1.0.0 + + + + + com.aliyun.oss + aliyun-sdk-oss + + + + + diff --git a/kernel-d-file/file-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/file/aliyun/AliyunFileOperator.java b/kernel-d-file/file-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/file/aliyun/AliyunFileOperator.java new file mode 100644 index 000000000..332567794 --- /dev/null +++ b/kernel-d-file/file-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/file/aliyun/AliyunFileOperator.java @@ -0,0 +1,200 @@ +package cn.stylefeng.roses.kernel.file.aliyun; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.file.FileOperatorApi; +import cn.stylefeng.roses.kernel.file.enums.BucketAuthEnum; +import cn.stylefeng.roses.kernel.file.exception.FileException; +import cn.stylefeng.roses.kernel.file.exception.enums.FileExceptionEnum; +import cn.stylefeng.roses.kernel.file.pojo.props.AliyunOssProperties; +import com.aliyun.oss.ClientException; +import com.aliyun.oss.OSS; +import com.aliyun.oss.OSSClientBuilder; +import com.aliyun.oss.OSSException; +import com.aliyun.oss.model.CannedAccessControlList; +import com.aliyun.oss.model.OSSObject; +import com.aliyun.oss.model.PutObjectRequest; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; +import java.util.Date; + +/** + * 阿里云文件操作的实现 + * + * @author fengshuonan + * @date 2020/10/26 10:59 + */ +public class AliyunFileOperator implements FileOperatorApi { + + /** + * 阿里云文件操作客户端 + */ + private OSS ossClient; + + /** + * 阿里云oss的配置 + */ + private final AliyunOssProperties aliyunOssProperties; + + public AliyunFileOperator(AliyunOssProperties aliyunOssProperties) { + this.aliyunOssProperties = aliyunOssProperties; + this.initClient(); + } + + @Override + public void initClient() { + String endpoint = aliyunOssProperties.getEndPoint(); + String accessKeyId = aliyunOssProperties.getAccessKeyId(); + String accessKeySecret = aliyunOssProperties.getAccessKeySecret(); + + // 创建OSSClient实例。 + ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + } + + @Override + public void destroyClient() { + ossClient.shutdown(); + } + + @Override + public Object getClient() { + return ossClient; + } + + @Override + public boolean doesBucketExist(String bucketName) { + try { + return ossClient.doesBucketExist(bucketName); + } catch (OSSException | ClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.ALIYUN_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.ALIYUN_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public void setBucketAcl(String bucketName, BucketAuthEnum bucketAuthEnum) { + try { + if (bucketAuthEnum.equals(BucketAuthEnum.PRIVATE)) { + ossClient.setBucketAcl(bucketName, CannedAccessControlList.Private); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ)) { + ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ_WRITE)) { + ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicReadWrite); + } + } catch (OSSException | ClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.ALIYUN_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.ALIYUN_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public boolean isExistingFile(String bucketName, String key) { + try { + return ossClient.doesObjectExist(bucketName, key); + } catch (OSSException | ClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.ALIYUN_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.ALIYUN_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public void storageFile(String bucketName, String key, byte[] bytes) { + try { + ossClient.putObject(bucketName, key, new ByteArrayInputStream(bytes)); + } catch (OSSException | ClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.ALIYUN_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.ALIYUN_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public void storageFile(String bucketName, String key, InputStream inputStream) { + try { + PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, inputStream); + ossClient.putObject(putObjectRequest); + } catch (OSSException | ClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.ALIYUN_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.ALIYUN_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public byte[] getFileBytes(String bucketName, String key) { + InputStream objectContent = null; + try { + OSSObject ossObject = ossClient.getObject(bucketName, key); + objectContent = ossObject.getObjectContent(); + return IoUtil.readBytes(objectContent); + } catch (OSSException | ClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.ALIYUN_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.ALIYUN_FILE_ERROR.getErrorCode(), finalUserTip); + } finally { + IoUtil.close(objectContent); + } + + } + + @Override + public void setFileAcl(String bucketName, String key, BucketAuthEnum bucketAuthEnum) { + try { + if (bucketAuthEnum.equals(BucketAuthEnum.PRIVATE)) { + ossClient.setObjectAcl(bucketName, key, CannedAccessControlList.Private); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ)) { + ossClient.setObjectAcl(bucketName, key, CannedAccessControlList.PublicRead); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ_WRITE)) { + ossClient.setObjectAcl(bucketName, key, CannedAccessControlList.PublicReadWrite); + } + } catch (OSSException | ClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.ALIYUN_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.ALIYUN_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) { + try { + ossClient.copyObject(originBucketName, originFileKey, newBucketName, newFileKey); + } catch (OSSException | ClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.ALIYUN_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.ALIYUN_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public String getFileAuthUrl(String bucketName, String key, Long timeoutMillis) { + try { + Date expiration = new Date(new Date().getTime() + timeoutMillis); + URL url = ossClient.generatePresignedUrl(bucketName, key, expiration); + return url.toString(); + } catch (OSSException | ClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.ALIYUN_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.ALIYUN_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public void deleteFile(String bucketName, String key) { + ossClient.deleteObject(bucketName, key); + } + +} diff --git a/kernel-d-file/file-sdk-local/README.md b/kernel-d-file/file-sdk-local/README.md new file mode 100644 index 000000000..d4b0dbd2e --- /dev/null +++ b/kernel-d-file/file-sdk-local/README.md @@ -0,0 +1 @@ +文件模块,本地存储文件的实现 \ No newline at end of file diff --git a/kernel-d-file/file-sdk-local/pom.xml b/kernel-d-file/file-sdk-local/pom.xml new file mode 100644 index 000000000..d41f2a661 --- /dev/null +++ b/kernel-d-file/file-sdk-local/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-file + 1.0.0 + ../pom.xml + + + file-sdk-local + + jar + + + + + + cn.stylefeng.roses + file-api + 1.0.0 + + + + + + cn.stylefeng.roses + jwt-sdk + 1.0.0 + + + + + diff --git a/kernel-d-file/file-sdk-local/src/main/java/cn/stylefeng/roses/kernel/file/local/LocalFileOperator.java b/kernel-d-file/file-sdk-local/src/main/java/cn/stylefeng/roses/kernel/file/local/LocalFileOperator.java new file mode 100644 index 000000000..1705fb1c9 --- /dev/null +++ b/kernel-d-file/file-sdk-local/src/main/java/cn/stylefeng/roses/kernel/file/local/LocalFileOperator.java @@ -0,0 +1,179 @@ +package cn.stylefeng.roses.kernel.file.local; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.system.SystemUtil; +import cn.stylefeng.roses.kernel.file.FileOperatorApi; +import cn.stylefeng.roses.kernel.file.constants.FileConstants; +import cn.stylefeng.roses.kernel.file.enums.BucketAuthEnum; +import cn.stylefeng.roses.kernel.file.exception.FileException; +import cn.stylefeng.roses.kernel.file.exception.enums.FileExceptionEnum; +import cn.stylefeng.roses.kernel.file.expander.FileConfigExpander; +import cn.stylefeng.roses.kernel.file.pojo.props.LocalFileProperties; +import cn.stylefeng.roses.kernel.jwt.JwtTokenOperator; +import cn.stylefeng.roses.kernel.jwt.api.pojo.config.JwtConfig; + +import java.io.File; +import java.io.InputStream; +import java.util.HashMap; + +/** + * 本地文件的操作 + * + * @author fengshuonan + * @date 2020/10/26 13:38 + */ +public class LocalFileOperator implements FileOperatorApi { + + private final LocalFileProperties localFileProperties; + + private String currentSavePath = ""; + + public LocalFileOperator(LocalFileProperties localFileProperties) { + this.localFileProperties = localFileProperties; + initClient(); + } + + @Override + public void initClient() { + if (SystemUtil.getOsInfo().isWindows()) { + String savePathWindows = localFileProperties.getLocalFileSavePathWin(); + if (!FileUtil.exist(savePathWindows)) { + FileUtil.mkdir(savePathWindows); + } + currentSavePath = savePathWindows; + } else { + String savePathLinux = localFileProperties.getLocalFileSavePathLinux(); + if (!FileUtil.exist(savePathLinux)) { + FileUtil.mkdir(savePathLinux); + } + currentSavePath = savePathLinux; + } + } + + @Override + public void destroyClient() { + // empty + } + + @Override + public Object getClient() { + // empty + return null; + } + + @Override + public boolean doesBucketExist(String bucketName) { + String absolutePath = currentSavePath + File.separator + bucketName; + return FileUtil.exist(absolutePath); + } + + @Override + public void setBucketAcl(String bucketName, BucketAuthEnum bucketAuthEnum) { + // empty + } + + @Override + public boolean isExistingFile(String bucketName, String key) { + String absoluteFile = currentSavePath + File.separator + bucketName + File.separator + key; + return FileUtil.exist(absoluteFile); + } + + @Override + public void storageFile(String bucketName, String key, byte[] bytes) { + + // 判断bucket存在不存在 + String bucketPath = currentSavePath + File.separator + bucketName; + if (!FileUtil.exist(bucketPath)) { + FileUtil.mkdir(bucketPath); + } + + // 存储文件 + String absoluteFile = currentSavePath + File.separator + bucketName + File.separator + key; + FileUtil.writeBytes(bytes, absoluteFile); + } + + @Override + public void storageFile(String bucketName, String key, InputStream inputStream) { + + // 判断bucket存在不存在 + String bucketPath = currentSavePath + File.separator + bucketName; + if (!FileUtil.exist(bucketPath)) { + FileUtil.mkdir(bucketPath); + } + + // 存储文件 + String absoluteFile = currentSavePath + File.separator + bucketName + File.separator + key; + FileUtil.writeFromStream(inputStream, absoluteFile); + } + + @Override + public byte[] getFileBytes(String bucketName, String key) { + + // 判断文件存在不存在 + String absoluteFile = currentSavePath + File.separator + bucketName + File.separator + key; + if (!FileUtil.exist(absoluteFile)) { + // 组装返回信息 + String errorMessage = StrUtil.format("bucket={},key={}", bucketName, key); + String userTip = FileExceptionEnum.FILE_NOT_FOUND.getUserTip(); + String finalUserTip = StrUtil.format(userTip, errorMessage); + throw new FileException(FileExceptionEnum.FILE_NOT_FOUND.getErrorCode(), finalUserTip); + } else { + return FileUtil.readBytes(absoluteFile); + } + } + + @Override + public void setFileAcl(String bucketName, String key, BucketAuthEnum bucketAuthEnum) { + // empty + } + + @Override + public void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) { + + // 判断文件存在不存在 + String originFile = currentSavePath + File.separator + originBucketName + File.separator + originFileKey; + if (!FileUtil.exist(originFile)) { + // 组装返回信息 + String errorMessage = StrUtil.format("bucket={},key={}", originBucketName, originFileKey); + String userTip = FileExceptionEnum.FILE_NOT_FOUND.getUserTip(); + String finalUserTip = StrUtil.format(userTip, errorMessage); + throw new FileException(FileExceptionEnum.FILE_NOT_FOUND.getErrorCode(), finalUserTip); + } else { + + // 拷贝文件 + String destFile = currentSavePath + File.separator + newBucketName + File.separator + newFileKey; + FileUtil.copy(originFile, destFile, true); + } + } + + @Override + public String getFileAuthUrl(String bucketName, String key, Long timeoutMillis) { + + // 初始化jwt token的生成工具 + JwtConfig jwtConfig = new JwtConfig(); + jwtConfig.setJwtSecret(FileConfigExpander.getFileAuthJwtSecret()); + jwtConfig.setExpiredSeconds(timeoutMillis / 1000); + JwtTokenOperator jwtTokenOperator = new JwtTokenOperator(jwtConfig); + + // 生成token + String token = jwtTokenOperator.generateToken(new HashMap<>()); + + // 拼接url = “host” + “预览图片的url” + “?token=xxx” + return FileConfigExpander.getServerDeployHost() + FileConstants.FILE_PREVIEW_URL + "?token=" + token; + } + + @Override + public void deleteFile(String bucketName, String key) { + + // 判断文件存在不存在 + String file = currentSavePath + File.separator + bucketName + File.separator + key; + if (!FileUtil.exist(file)) { + return; + } + + // 删除文件 + FileUtil.del(file); + + } +} diff --git a/kernel-d-file/file-sdk-minio/README.md b/kernel-d-file/file-sdk-minio/README.md new file mode 100644 index 000000000..1be781066 --- /dev/null +++ b/kernel-d-file/file-sdk-minio/README.md @@ -0,0 +1 @@ +文件模块,minio服务器的实现 \ No newline at end of file diff --git a/kernel-d-file/file-sdk-minio/pom.xml b/kernel-d-file/file-sdk-minio/pom.xml new file mode 100644 index 000000000..aa7279da6 --- /dev/null +++ b/kernel-d-file/file-sdk-minio/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-file + 1.0.0 + ../pom.xml + + + file-sdk-minio + + jar + + + + + + cn.stylefeng.roses + file-api + 1.0.0 + + + + + + cn.stylefeng.roses + jwt-sdk + 1.0.0 + + + + + com.amazonaws + aws-java-sdk-s3 + + + io.minio + minio + + + + + diff --git a/kernel-d-file/file-sdk-minio/src/main/java/cn/stylefeng/roses/kernel/file/minio/MinIoFileOperator.java b/kernel-d-file/file-sdk-minio/src/main/java/cn/stylefeng/roses/kernel/file/minio/MinIoFileOperator.java new file mode 100644 index 000000000..48c4cc83f --- /dev/null +++ b/kernel-d-file/file-sdk-minio/src/main/java/cn/stylefeng/roses/kernel/file/minio/MinIoFileOperator.java @@ -0,0 +1,275 @@ +package cn.stylefeng.roses.kernel.file.minio; + +import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.file.FileOperatorApi; +import cn.stylefeng.roses.kernel.file.constants.FileConstants; +import cn.stylefeng.roses.kernel.file.exception.FileException; +import cn.stylefeng.roses.kernel.file.exception.enums.FileExceptionEnum; +import cn.stylefeng.roses.kernel.file.expander.FileConfigExpander; +import cn.stylefeng.roses.kernel.file.pojo.props.MinIoProperties; +import cn.stylefeng.roses.kernel.file.enums.BucketAuthEnum; +import cn.stylefeng.roses.kernel.jwt.JwtTokenOperator; +import cn.stylefeng.roses.kernel.jwt.api.pojo.config.JwtConfig; +import io.minio.MinioClient; +import io.minio.errors.InvalidEndpointException; +import io.minio.errors.InvalidPortException; +import io.minio.policy.PolicyType; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * MinIo文件操作客户端 + * + * @author fengshuonan + * @date 2020/10/31 10:35 + */ +public class MinIoFileOperator implements FileOperatorApi { + + private final Object LOCK = new Object(); + + /** + * 文件ContentType对应关系 + */ + Map contentType = new HashMap<>(); + + /** + * MinIo文件操作客户端 + */ + private MinioClient minioClient; + + /** + * MinIo的配置 + */ + private final MinIoProperties minIoProperties; + + public MinIoFileOperator(MinIoProperties minIoProperties) { + this.minIoProperties = minIoProperties; + this.initClient(); + } + + @Override + public void initClient() { + String endpoint = minIoProperties.getEndpoint(); + String accessKey = minIoProperties.getAccessKey(); + String secretKey = minIoProperties.getSecretKey(); + + // 创建minioClient实例 + try { + minioClient = new MinioClient(endpoint, accessKey, secretKey); + } catch (InvalidEndpointException | InvalidPortException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.MINIO_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.MINIO_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public void destroyClient() { + // empty + } + + @Override + public Object getClient() { + return minioClient; + } + + @Override + public boolean doesBucketExist(String bucketName) { + try { + return minioClient.bucketExists(bucketName); + } catch (Exception e) { + // 组装提示信息 + String userTip = FileExceptionEnum.MINIO_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.MINIO_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public void setBucketAcl(String bucketName, BucketAuthEnum bucketAuthEnum) { + setFileAcl(bucketName, "*", bucketAuthEnum); + } + + @Override + public boolean isExistingFile(String bucketName, String key) { + InputStream inputStream = null; + try { + inputStream = minioClient.getObject(bucketName, key); + if (inputStream != null) { + return true; + } + } catch (Exception e) { + return false; + } finally { + IoUtil.close(inputStream); + } + return false; + } + + @Override + public void storageFile(String bucketName, String key, byte[] bytes) { + if (bytes != null && bytes.length > 0) { + // 字节数组转字节数组输入流 + ByteArrayInputStream byteArrayInputStream = IoUtil.toStream(bytes); + + // 获取文件类型 + ByteArrayInputStream tmp = IoUtil.toStream(bytes); + String type = FileTypeUtil.getType(tmp); + String fileContentType = getFileContentType(String.format("%s%s", ".", type)); + + try { + minioClient.putObject(bucketName, key, byteArrayInputStream, bytes.length, fileContentType); + } catch (Exception e) { + + // 组装提示信息 + String userTip = FileExceptionEnum.MINIO_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.MINIO_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + } + + @Override + public void storageFile(String bucketName, String key, InputStream inputStream) { + if (inputStream != null) { + byte[] bytes = IoUtil.readBytes(inputStream); + storageFile(bucketName, key, bytes); + } + } + + @Override + public byte[] getFileBytes(String bucketName, String key) { + try { + InputStream inputStream = minioClient.getObject(bucketName, key); + return IoUtil.readBytes(inputStream); + } catch (Exception e) { + // 组装提示信息 + String userTip = FileExceptionEnum.MINIO_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.MINIO_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public void setFileAcl(String bucketName, String key, BucketAuthEnum bucketAuthEnum) { + try { + if (bucketAuthEnum.equals(BucketAuthEnum.PRIVATE)) { + minioClient.setBucketPolicy(bucketName, key, PolicyType.NONE); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ)) { + minioClient.setBucketPolicy(bucketName, key, PolicyType.READ_ONLY); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ_WRITE)) { + minioClient.setBucketPolicy(bucketName, key, PolicyType.READ_WRITE); + } else if (bucketAuthEnum.equals(BucketAuthEnum.MINIO_WRITE_ONLY)) { + minioClient.setBucketPolicy(bucketName, key, PolicyType.WRITE_ONLY); + } + } catch (Exception e) { + + // 组装提示信息 + String userTip = FileExceptionEnum.MINIO_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.MINIO_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) { + try { + minioClient.copyObject(originBucketName, originFileKey, newBucketName, newFileKey); + } catch (Exception e) { + // 组装提示信息 + String userTip = FileExceptionEnum.MINIO_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.MINIO_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public String getFileAuthUrl(String bucketName, String key, Long timeoutMillis) { + + // 初始化jwt token的生成工具 + JwtConfig jwtConfig = new JwtConfig(); + jwtConfig.setJwtSecret(FileConfigExpander.getFileAuthJwtSecret()); + jwtConfig.setExpiredSeconds(timeoutMillis / 1000); + JwtTokenOperator jwtTokenOperator = new JwtTokenOperator(jwtConfig); + + // 生成token + String token = jwtTokenOperator.generateToken(new HashMap<>()); + + // 拼接url = “host” + “预览图片的url” + “?token=xxx” + return FileConfigExpander.getServerDeployHost() + FileConstants.FILE_PREVIEW_URL + "?token=" + token; + + } + + @Override + public void deleteFile(String bucketName, String key) { + try { + minioClient.removeObject(bucketName, key); + } catch (Exception e) { + // 组装提示信息 + String userTip = FileExceptionEnum.MINIO_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.MINIO_FILE_ERROR.getErrorCode(), finalUserTip); + } + + } + + /** + * 获取文件后缀对应的contentType + * + * @author fengshuonan + * @date 2020/11/2 18:08 + */ + private Map getFileContentType() { + synchronized (LOCK) { + if (contentType.size() == 0) { + contentType.put(".bmp", "application/x-bmp"); + contentType.put(".gif", "image/gif"); + contentType.put(".fax", "image/fax"); + contentType.put(".ico", "image/x-icon"); + contentType.put(".jfif", "image/jpeg"); + contentType.put(".jpe", "image/jpeg"); + contentType.put(".jpeg", "image/jpeg"); + contentType.put(".jpg", "image/jpeg"); + contentType.put(".png", "image/png"); + contentType.put(".rp", "image/vnd.rn-realpix"); + contentType.put(".tif", "image/tiff"); + contentType.put(".tiff", "image/tiff"); + contentType.put(".doc", "application/msword"); + contentType.put(".ppt", "application/x-ppt"); + contentType.put(".pdf", "application/pdf"); + contentType.put(".xls", "application/vnd.ms-excel"); + contentType.put(".txt", "text/plain"); + contentType.put(".java", "java/*"); + contentType.put(".html", "text/html"); + contentType.put(".avi", "video/avi"); + contentType.put(".movie", "video/x-sgi-movie"); + contentType.put(".mp4", "video/mpeg4"); + contentType.put(".mp3", "audio/mp3"); + } + } + return contentType; + } + + /** + * 获取文件后缀对应的contentType + * + * @author fengshuonan + * @date 2020/11/2 18:05 + */ + private String getFileContentType(String fileSuffix) { + String contentType = getFileContentType().get(fileSuffix); + if (ObjectUtil.isEmpty(contentType)) { + return "application/octet-stream"; + } else { + return contentType; + } + } + +} diff --git a/kernel-d-file/file-sdk-tencent/README.md b/kernel-d-file/file-sdk-tencent/README.md new file mode 100644 index 000000000..1d1449b62 --- /dev/null +++ b/kernel-d-file/file-sdk-tencent/README.md @@ -0,0 +1 @@ +文件模块,腾讯云文件的实现 \ No newline at end of file diff --git a/kernel-d-file/file-sdk-tencent/pom.xml b/kernel-d-file/file-sdk-tencent/pom.xml new file mode 100644 index 000000000..74ef82415 --- /dev/null +++ b/kernel-d-file/file-sdk-tencent/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-file + 1.0.0 + ../pom.xml + + + file-sdk-tencent + + jar + + + + + + cn.stylefeng.roses + file-api + 1.0.0 + + + + + com.qcloud + cos_api + + + commons-logging + commons-logging + + + + + diff --git a/kernel-d-file/file-sdk-tencent/src/main/java/cn/stylefeng/roses/kernel/file/tencent/TenFileOperator.java b/kernel-d-file/file-sdk-tencent/src/main/java/cn/stylefeng/roses/kernel/file/tencent/TenFileOperator.java new file mode 100644 index 000000000..7fe193e0e --- /dev/null +++ b/kernel-d-file/file-sdk-tencent/src/main/java/cn/stylefeng/roses/kernel/file/tencent/TenFileOperator.java @@ -0,0 +1,247 @@ +package cn.stylefeng.roses.kernel.file.tencent; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import com.qcloud.cos.COSClient; +import com.qcloud.cos.ClientConfig; +import com.qcloud.cos.auth.BasicCOSCredentials; +import com.qcloud.cos.auth.COSCredentials; +import com.qcloud.cos.exception.CosClientException; +import com.qcloud.cos.exception.CosServiceException; +import com.qcloud.cos.http.HttpMethodName; +import com.qcloud.cos.model.*; +import com.qcloud.cos.region.Region; +import com.qcloud.cos.transfer.TransferManager; +import com.qcloud.cos.transfer.TransferManagerConfiguration; +import cn.stylefeng.roses.kernel.file.FileOperatorApi; +import cn.stylefeng.roses.kernel.file.enums.BucketAuthEnum; +import cn.stylefeng.roses.kernel.file.exception.FileException; +import cn.stylefeng.roses.kernel.file.exception.enums.FileExceptionEnum; +import cn.stylefeng.roses.kernel.file.pojo.props.TenCosProperties; + +import javax.activation.MimetypesFileTypeMap; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; +import java.util.Date; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * 腾讯云内网文件操作 + * + * @author fengshuonan + * @date 2020-05-22-6:51 下午 + */ +public class TenFileOperator implements FileOperatorApi { + + private final TenCosProperties tenCosProperties; + + private COSClient cosClient; + + private TransferManager transferManager; + + public TenFileOperator(TenCosProperties tenCosProperties) { + this.tenCosProperties = tenCosProperties; + initClient(); + } + + @Override + public void initClient() { + + // 1.初始化用户身份信息 + String secretId = tenCosProperties.getSecretId(); + String secretKey = tenCosProperties.getSecretKey(); + COSCredentials cred = new BasicCOSCredentials(secretId, secretKey); + + // 2.设置 bucket 的区域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224 + Region region = new Region(tenCosProperties.getRegionId()); + ClientConfig clientConfig = new ClientConfig(region); + + // 3.生成 cos 客户端。 + cosClient = new COSClient(cred, clientConfig); + + // 4.线程池大小,建议在客户端与 COS 网络充足(例如使用腾讯云的 CVM,同地域上传 COS)的情况下,设置成16或32即可,可较充分的利用网络资源 + // 对于使用公网传输且网络带宽质量不高的情况,建议减小该值,避免因网速过慢,造成请求超时。 + ExecutorService threadPool = Executors.newFixedThreadPool(32); + + // 5.传入一个 threadpool, 若不传入线程池,默认 TransferManager 中会生成一个单线程的线程池。 + transferManager = new TransferManager(cosClient, threadPool); + + // 6.设置高级接口的分块上传阈值和分块大小为10MB + TransferManagerConfiguration transferManagerConfiguration = new TransferManagerConfiguration(); + transferManagerConfiguration.setMultipartUploadThreshold(10 * 1024 * 1024); + transferManagerConfiguration.setMinimumUploadPartSize(10 * 1024 * 1024); + transferManager.setConfiguration(transferManagerConfiguration); + } + + @Override + public void destroyClient() { + cosClient.shutdown(); + } + + @Override + public Object getClient() { + return cosClient; + } + + @Override + public boolean doesBucketExist(String bucketName) { + try { + return cosClient.doesBucketExist(bucketName); + } catch (CosClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.TENCENT_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.TENCENT_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public void setBucketAcl(String bucketName, BucketAuthEnum bucketAuthEnum) { + try { + if (bucketAuthEnum.equals(BucketAuthEnum.PRIVATE)) { + cosClient.setBucketAcl(bucketName, CannedAccessControlList.Private); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ)) { + cosClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ_WRITE)) { + cosClient.setBucketAcl(bucketName, CannedAccessControlList.PublicReadWrite); + } + } catch (CosClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.TENCENT_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.TENCENT_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public boolean isExistingFile(String bucketName, String key) { + try { + cosClient.getObjectMetadata(bucketName, key); + return true; + } catch (CosServiceException e) { + return false; + } + } + + @Override + public void storageFile(String bucketName, String key, byte[] bytes) { + // 根据文件名获取contentType + String contentType = "application/octet-stream"; + if (key.contains(".")) { + contentType = MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(key); + } + + // 上传文件 + ByteArrayInputStream byteArrayInputStream = null; + try { + byteArrayInputStream = new ByteArrayInputStream(bytes); + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentType(contentType); + cosClient.putObject(bucketName, key, new ByteArrayInputStream(bytes), objectMetadata); + } catch (CosClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.TENCENT_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.TENCENT_FILE_ERROR.getErrorCode(), finalUserTip); + } finally { + IoUtil.close(byteArrayInputStream); + } + + } + + @Override + public void storageFile(String bucketName, String key, InputStream inputStream) { + + // 根据文件名获取contentType + String contentType = "application/octet-stream"; + if (key.contains(".")) { + contentType = MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(key); + } + + // 上传文件 + try { + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentType(contentType); + cosClient.putObject(bucketName, key, inputStream, objectMetadata); + } catch (CosClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.TENCENT_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.TENCENT_FILE_ERROR.getErrorCode(), finalUserTip); + } finally { + IoUtil.close(inputStream); + } + } + + @Override + public byte[] getFileBytes(String bucketName, String key) { + COSObjectInputStream cosObjectInput = null; + try { + GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key); + COSObject cosObject = cosClient.getObject(getObjectRequest); + cosObjectInput = cosObject.getObjectContent(); + return IoUtil.readBytes(cosObjectInput); + } catch (CosClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.TENCENT_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.TENCENT_FILE_ERROR.getErrorCode(), finalUserTip); + } finally { + IoUtil.close(cosObjectInput); + } + } + + @Override + public void setFileAcl(String bucketName, String key, BucketAuthEnum bucketAuthEnum) { + if (bucketAuthEnum.equals(BucketAuthEnum.PRIVATE)) { + cosClient.setObjectAcl(bucketName, key, CannedAccessControlList.Private); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ)) { + cosClient.setObjectAcl(bucketName, key, CannedAccessControlList.PublicRead); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ_WRITE)) { + cosClient.setObjectAcl(bucketName, key, CannedAccessControlList.PublicReadWrite); + } + } + + @Override + public void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) { + // 初始化拷贝参数 + Region srcBucketRegion = new Region(tenCosProperties.getRegionId()); + CopyObjectRequest copyObjectRequest = new CopyObjectRequest( + srcBucketRegion, originBucketName, originFileKey, newBucketName, newFileKey); + + // 拷贝对象 + try { + transferManager.copy(copyObjectRequest, cosClient, null); + } catch (CosClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.TENCENT_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.TENCENT_FILE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public String getFileAuthUrl(String bucketName, String key, Long timeoutMillis) { + GeneratePresignedUrlRequest presignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, key, HttpMethodName.GET); + Date expirationDate = new Date(System.currentTimeMillis() + timeoutMillis); + presignedUrlRequest.setExpiration(expirationDate); + URL url = null; + try { + url = cosClient.generatePresignedUrl(presignedUrlRequest); + } catch (CosClientException e) { + // 组装提示信息 + String userTip = FileExceptionEnum.TENCENT_FILE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new FileException(FileExceptionEnum.TENCENT_FILE_ERROR.getErrorCode(), finalUserTip); + } + return url.toString(); + } + + @Override + public void deleteFile(String bucketName, String key) { + cosClient.deleteObject(bucketName, key); + } + +} diff --git a/kernel-d-file/file-spring-boot-starter/README.md b/kernel-d-file/file-spring-boot-starter/README.md new file mode 100644 index 000000000..a45a41dde --- /dev/null +++ b/kernel-d-file/file-spring-boot-starter/README.md @@ -0,0 +1 @@ +文件的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-file/file-spring-boot-starter/pom.xml b/kernel-d-file/file-spring-boot-starter/pom.xml new file mode 100644 index 000000000..5a1e1e008 --- /dev/null +++ b/kernel-d-file/file-spring-boot-starter/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-file + 1.0.0 + ../pom.xml + + + file-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + file-business + 1.0.0 + + + + + cn.stylefeng.roses + file-sdk-local + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-file/file-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/file/starter/GunsFileAutoConfiguration.java b/kernel-d-file/file-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/file/starter/GunsFileAutoConfiguration.java new file mode 100644 index 000000000..c290d14a5 --- /dev/null +++ b/kernel-d-file/file-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/file/starter/GunsFileAutoConfiguration.java @@ -0,0 +1,39 @@ +package cn.stylefeng.roses.kernel.file.starter; + +import cn.stylefeng.roses.kernel.file.FileOperatorApi; +import cn.stylefeng.roses.kernel.file.expander.FileConfigExpander; +import cn.stylefeng.roses.kernel.file.local.LocalFileOperator; +import cn.stylefeng.roses.kernel.file.pojo.props.LocalFileProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 文件的自动配置 + * + * @author fengshuonan + * @date 2020/12/1 14:34 + */ +@Configuration +public class GunsFileAutoConfiguration { + + /** + * 本地文件操作 + * + * @author fengshuonan + * @date 2020/12/1 14:40 + */ + @Bean + @ConditionalOnMissingBean(FileOperatorApi.class) + public FileOperatorApi fileOperatorApi() { + + LocalFileProperties localFileProperties = new LocalFileProperties(); + + // 从系统配置表中读取配置 + localFileProperties.setLocalFileSavePathLinux(FileConfigExpander.getLocalFileSavePathLinux()); + localFileProperties.setLocalFileSavePathWin(FileConfigExpander.getLocalFileSavePathWindows()); + + return new LocalFileOperator(localFileProperties); + } + +} diff --git a/kernel-d-file/file-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-file/file-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..0e39011ef --- /dev/null +++ b/kernel-d-file/file-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.file.starter.GunsFileAutoConfiguration \ No newline at end of file diff --git a/kernel-d-file/pom.xml b/kernel-d-file/pom.xml new file mode 100644 index 000000000..1c6fa38d9 --- /dev/null +++ b/kernel-d-file/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-file + + pom + + + file-api + file-business + file-sdk-aliyun + file-sdk-tencent + file-sdk-local + file-sdk-minio + file-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-jwt/README.md b/kernel-d-jwt/README.md new file mode 100644 index 000000000..f50feb915 --- /dev/null +++ b/kernel-d-jwt/README.md @@ -0,0 +1,3 @@ +# jwt模块 + +用于针对jwt token的一系列操作 \ No newline at end of file diff --git a/kernel-d-jwt/jwt-api/README.md b/kernel-d-jwt/jwt-api/README.md new file mode 100644 index 000000000..f1bdc0815 --- /dev/null +++ b/kernel-d-jwt/jwt-api/README.md @@ -0,0 +1 @@ +jwt的api模块 \ No newline at end of file diff --git a/kernel-d-jwt/jwt-api/pom.xml b/kernel-d-jwt/jwt-api/pom.xml new file mode 100644 index 000000000..8f1ae631a --- /dev/null +++ b/kernel-d-jwt/jwt-api/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-jwt + 1.0.0 + ../pom.xml + + + jwt-api + + jar + + + + + + cn.stylefeng.roses + config-api + 1.0.0 + + + + + io.jsonwebtoken + jjwt + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/JwtApi.java b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/JwtApi.java new file mode 100644 index 000000000..1c458e360 --- /dev/null +++ b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/JwtApi.java @@ -0,0 +1,86 @@ +package cn.stylefeng.roses.kernel.jwt.api; + +import cn.stylefeng.roses.kernel.jwt.api.exception.JwtException; +import cn.stylefeng.roses.kernel.jwt.api.pojo.payload.DefaultJwtPayload; + +import java.util.Map; + +/** + * jwt相关的操作api + * + * @author fengshuonan + * @date 2020/10/21 11:31 + */ +public interface JwtApi { + + /** + * 生成token + * + * @param payload jwt的载体信息 + * @return jwt token + * @author fengshuonan + * @date 2020/10/21 11:38 + */ + String generateToken(Map payload); + + /** + * 生成token,用默认的payload格式 + * + * @param defaultJwtPayload jwt的载体信息 + * @return jwt token + * @author fengshuonan + * @date 2020/10/21 11:38 + */ + String generateTokenDefaultPayload(DefaultJwtPayload defaultJwtPayload); + + /** + * 获取jwt的payload(通用的) + * + * @param token jwt的token + * @return jwt的payload + * @author fengshuonan + * @date 2020/10/21 11:52 + */ + Map getJwtPayloadClaims(String token); + + /** + * 获取jwt的payload(限定默认格式) + * + * @param token jwt的token + * @return 返回默认格式的payload + * @author fengshuonan + * @date 2020/10/21 11:51 + */ + DefaultJwtPayload getDefaultPayload(String token); + + /** + * 校验jwt token是否正确 + * + * @param token jwt的token + * @return true-token正确,false-token错误或失效 + * @author fengshuonan + * @date 2020/10/21 11:43 + */ + boolean validateToken(String token); + + /** + * 校验jwt token是否正确,如果jwt异常,或者jwt过期,则直接抛出异常 + * + * @param token jwt的token + * @throws JwtException Jwt相关的业务异常 + * @author fengshuonan + * @date 2020/10/21 11:43 + */ + void validateTokenWithException(String token) throws JwtException; + + /** + * 获取 token 的失效时间 + * + * @param token jwt token + * @return true-token失效,false-token没失效 + * @author fengshuonan + * @date 2020/10/21 11:56 + */ + boolean getTokenExpiredFlag(String token); + +} diff --git a/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/constants/JwtConstants.java b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/constants/JwtConstants.java new file mode 100644 index 000000000..0d8862b9e --- /dev/null +++ b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/constants/JwtConstants.java @@ -0,0 +1,26 @@ +package cn.stylefeng.roses.kernel.jwt.api.constants; + +/** + * jwt模块常量 + * + * @author fengshuonan + * @date 2020/10/16 11:05 + */ +public interface JwtConstants { + + /** + * jwt模块的名称 + */ + String JWT_MODULE_NAME = "kernel-d-jwt"; + + /** + * 异常枚举的步进值 + */ + String JWT_EXCEPTION_STEP_CODE = "06"; + + /** + * jwt默认失效时间 1天 + */ + Long DEFAULT_JWT_TIMEOUT_SECONDS = 3600L * 24; + +} diff --git a/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/context/JwtContext.java b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/context/JwtContext.java new file mode 100644 index 000000000..6d9a4647c --- /dev/null +++ b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/context/JwtContext.java @@ -0,0 +1,24 @@ +package cn.stylefeng.roses.kernel.jwt.api.context; + +import cn.hutool.extra.spring.SpringUtil; +import cn.stylefeng.roses.kernel.jwt.api.JwtApi; + +/** + * Jwt工具的context,获取容器中的jwt工具类 + * + * @author fengshuonan + * @date 2020/10/21 14:07 + */ +public class JwtContext { + + /** + * 获取jwt操作接口 + * + * @author fengshuonan + * @date 2020/10/21 14:07 + */ + public static JwtApi me() { + return SpringUtil.getBean(JwtApi.class); + } + +} diff --git a/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/exception/JwtException.java b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/exception/JwtException.java new file mode 100644 index 000000000..6cb013133 --- /dev/null +++ b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/exception/JwtException.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.jwt.api.exception; + +import cn.stylefeng.roses.kernel.jwt.api.constants.JwtConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; + +/** + * jwt异常 + * + * @author fengshuonan + * @date 2020/10/15 15:59 + */ +public class JwtException extends ServiceException { + + public JwtException(AbstractExceptionEnum exception, String userTip) { + super(JwtConstants.JWT_MODULE_NAME, exception.getErrorCode(), userTip); + } + + public JwtException(AbstractExceptionEnum exception) { + super(JwtConstants.JWT_MODULE_NAME, exception); + } + +} diff --git a/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/exception/enums/JwtExceptionEnum.java b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/exception/enums/JwtExceptionEnum.java new file mode 100644 index 000000000..70faa319d --- /dev/null +++ b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/exception/enums/JwtExceptionEnum.java @@ -0,0 +1,37 @@ +package cn.stylefeng.roses.kernel.jwt.api.exception.enums; + +import cn.stylefeng.roses.kernel.jwt.api.constants.JwtConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import lombok.Getter; + +/** + * jwt异常的状态码 + * + * @author fengshuonan + * @date 2020/10/16 10:53 + */ +@Getter +public enum JwtExceptionEnum implements AbstractExceptionEnum { + + /** + * jwt解析异常 + */ + JWT_PARSE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + JwtConstants.JWT_EXCEPTION_STEP_CODE + "01", "jwt解析错误!jwt为:{}"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + JwtExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/expander/JwtConfigExpander.java b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/expander/JwtConfigExpander.java new file mode 100644 index 000000000..597229eae --- /dev/null +++ b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/expander/JwtConfigExpander.java @@ -0,0 +1,43 @@ +package cn.stylefeng.roses.kernel.jwt.api.expander; + +import cn.hutool.core.util.RandomUtil; +import cn.stylefeng.roses.kernel.config.api.context.ConfigContext; + +import static cn.stylefeng.roses.kernel.jwt.api.constants.JwtConstants.DEFAULT_JWT_TIMEOUT_SECONDS; + +/** + * jwt工具类的配置获取 + * + * @author fengshuonan + * @date 2020/12/1 15:05 + */ +public class JwtConfigExpander { + + /** + * 获取jwt的密钥 + * + * @author fengshuonan + * @date 2020/12/1 15:07 + */ + public static String getJwtSecret() { + String sysJwtSecret = ConfigContext.me().getConfigValueNullable("SYS_JWT_SECRET", String.class); + + // 没配置就返回一个随机密码 + if (sysJwtSecret == null) { + return RandomUtil.randomString(20); + } else { + return sysJwtSecret; + } + } + + /** + * jwt失效时间,默认1天 + * + * @author fengshuonan + * @date 2020/12/1 15:08 + */ + public static Long getJwtTimeoutSeconds() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_JWT_TIMEOUT_SECONDS", Long.class, DEFAULT_JWT_TIMEOUT_SECONDS); + } + +} diff --git a/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/pojo/config/JwtConfig.java b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/pojo/config/JwtConfig.java new file mode 100644 index 000000000..7de951327 --- /dev/null +++ b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/pojo/config/JwtConfig.java @@ -0,0 +1,24 @@ +package cn.stylefeng.roses.kernel.jwt.api.pojo.config; + +import lombok.Data; + +/** + * jwt相关的配置封装 + * + * @author fengshuonan + * @date 2020/10/21 11:37 + */ +@Data +public class JwtConfig { + + /** + * jwt秘钥 + */ + private String jwtSecret; + + /** + * 过期时间(秒) + */ + private Long expiredSeconds; + +} diff --git a/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/pojo/payload/DefaultJwtPayload.java b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/pojo/payload/DefaultJwtPayload.java new file mode 100644 index 000000000..2ca93d375 --- /dev/null +++ b/kernel-d-jwt/jwt-api/src/main/java/cn/stylefeng/roses/kernel/jwt/api/pojo/payload/DefaultJwtPayload.java @@ -0,0 +1,52 @@ +package cn.stylefeng.roses.kernel.jwt.api.pojo.payload; + +import cn.hutool.core.util.IdUtil; +import lombok.Data; + +import java.util.Map; + +/** + * jwt的载体,也就是jwt本身带的一些信息 + * + * @author fengshuonan + * @date 2020/10/21 11:37 + */ +@Data +public class DefaultJwtPayload { + + /** + * 用户id + */ + private Long userId; + + /** + * 账号 + */ + private String account; + + /** + * 唯一表示id, 用于缓存登录用户的唯一凭证 + */ + private String uuid; + + /** + * 是否记住我 + */ + private boolean rememberMe; + + /** + * 其他载体信息 + */ + private Map others; + + public DefaultJwtPayload() { + } + + public DefaultJwtPayload(Long userId, String account, boolean rememberMe) { + this.userId = userId; + this.account = account; + this.uuid = IdUtil.fastUUID(); + this.rememberMe = rememberMe; + } + +} diff --git a/kernel-d-jwt/jwt-sdk/README.md b/kernel-d-jwt/jwt-sdk/README.md new file mode 100644 index 000000000..8dce2ccb5 --- /dev/null +++ b/kernel-d-jwt/jwt-sdk/README.md @@ -0,0 +1 @@ +jwt模块的sdk \ No newline at end of file diff --git a/kernel-d-jwt/jwt-sdk/pom.xml b/kernel-d-jwt/jwt-sdk/pom.xml new file mode 100644 index 000000000..2a9ecfd56 --- /dev/null +++ b/kernel-d-jwt/jwt-sdk/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-jwt + 1.0.0 + ../pom.xml + + + jwt-sdk + + jar + + + + + + cn.stylefeng.roses + jwt-api + 1.0.0 + + + + + diff --git a/kernel-d-jwt/jwt-sdk/src/main/java/cn/stylefeng/roses/kernel/jwt/JwtTokenOperator.java b/kernel-d-jwt/jwt-sdk/src/main/java/cn/stylefeng/roses/kernel/jwt/JwtTokenOperator.java new file mode 100644 index 000000000..43ddcbc30 --- /dev/null +++ b/kernel-d-jwt/jwt-sdk/src/main/java/cn/stylefeng/roses/kernel/jwt/JwtTokenOperator.java @@ -0,0 +1,112 @@ +package cn.stylefeng.roses.kernel.jwt; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.jwt.api.JwtApi; +import cn.stylefeng.roses.kernel.jwt.api.exception.JwtException; +import cn.stylefeng.roses.kernel.jwt.api.pojo.config.JwtConfig; +import cn.stylefeng.roses.kernel.jwt.api.pojo.payload.DefaultJwtPayload; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +import java.util.Date; +import java.util.Map; + +import static cn.stylefeng.roses.kernel.jwt.api.exception.enums.JwtExceptionEnum.JWT_PARSE_ERROR; + +/** + * jwt token的操作对象 + * + * @author fengshuonan + * @date 2020/10/31 15:33 + */ +public class JwtTokenOperator implements JwtApi { + + private final JwtConfig jwtConfig; + + public JwtTokenOperator(JwtConfig jwtConfig) { + this.jwtConfig = jwtConfig; + } + + @Override + public String generateToken(Map payload) { + + // 计算过期时间 + DateTime expirationDate = DateUtil.offsetMillisecond(new Date(), Convert.toInt(jwtConfig.getExpiredSeconds()) * 1000); + + // 构造jwt token + return Jwts.builder() + .setClaims(payload) + .setIssuedAt(new Date()) + .setExpiration(expirationDate) + .signWith(SignatureAlgorithm.HS512, jwtConfig.getJwtSecret()) + .compact(); + } + + @Override + public String generateTokenDefaultPayload(DefaultJwtPayload defaultJwtPayload) { + + // 计算过期时间 + DateTime expirationDate = DateUtil.offsetMillisecond(new Date(), Convert.toInt(jwtConfig.getExpiredSeconds()) * 1000); + + // 构造jwt token + return Jwts.builder() + .setClaims(BeanUtil.beanToMap(defaultJwtPayload)) + .setSubject(defaultJwtPayload.getUserId().toString()) + .setIssuedAt(new Date()) + .setExpiration(expirationDate) + .signWith(SignatureAlgorithm.HS512, jwtConfig.getJwtSecret()) + .compact(); + } + + @Override + public Claims getJwtPayloadClaims(String token) { + return Jwts.parser() + .setSigningKey(jwtConfig.getJwtSecret()) + .parseClaimsJws(token) + .getBody(); + } + + @Override + public DefaultJwtPayload getDefaultPayload(String token) { + Map jwtPayload = getJwtPayloadClaims(token); + return BeanUtil.toBeanIgnoreError(jwtPayload, DefaultJwtPayload.class); + } + + @Override + public boolean validateToken(String token) { + try { + getJwtPayloadClaims(token); + return true; + } catch (io.jsonwebtoken.JwtException jwtException) { + return false; + } + } + + @Override + public void validateTokenWithException(String token) throws JwtException { + try { + getJwtPayloadClaims(token); + } catch (io.jsonwebtoken.JwtException jwtException) { + String userTip = StrUtil.format(JWT_PARSE_ERROR.getUserTip(), token); + throw new JwtException(JWT_PARSE_ERROR, userTip); + } + } + + @Override + public boolean getTokenExpiredFlag(String token) { + try { + Claims claims = getJwtPayloadClaims(token); + final Date expiration = claims.getExpiration(); + return expiration.before(new Date()); + } catch (ExpiredJwtException expiredJwtException) { + return true; + } + } + +} diff --git a/kernel-d-jwt/jwt-spring-boot-starter/README.md b/kernel-d-jwt/jwt-spring-boot-starter/README.md new file mode 100644 index 000000000..d351cf9f1 --- /dev/null +++ b/kernel-d-jwt/jwt-spring-boot-starter/README.md @@ -0,0 +1 @@ +jwt功能的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-jwt/jwt-spring-boot-starter/pom.xml b/kernel-d-jwt/jwt-spring-boot-starter/pom.xml new file mode 100644 index 000000000..d78b5c1a5 --- /dev/null +++ b/kernel-d-jwt/jwt-spring-boot-starter/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-jwt + 1.0.0 + ../pom.xml + + + jwt-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + jwt-sdk + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-jwt/jwt-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/jwt/starter/GunsJwtAutoConfiguration.java b/kernel-d-jwt/jwt-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/jwt/starter/GunsJwtAutoConfiguration.java new file mode 100644 index 000000000..c4b158d35 --- /dev/null +++ b/kernel-d-jwt/jwt-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/jwt/starter/GunsJwtAutoConfiguration.java @@ -0,0 +1,39 @@ +package cn.stylefeng.roses.kernel.jwt.starter; + +import cn.stylefeng.roses.kernel.jwt.JwtTokenOperator; +import cn.stylefeng.roses.kernel.jwt.api.JwtApi; +import cn.stylefeng.roses.kernel.jwt.api.expander.JwtConfigExpander; +import cn.stylefeng.roses.kernel.jwt.api.pojo.config.JwtConfig; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * jwt的自动配置 + * + * @author fengshuonan + * @date 2020/12/1 14:34 + */ +@Configuration +public class GunsJwtAutoConfiguration { + + /** + * jwt操作工具类的配置 + * + * @author fengshuonan + * @date 2020/12/1 14:40 + */ + @Bean + @ConditionalOnMissingBean(JwtApi.class) + public JwtApi jwtApi() { + + JwtConfig jwtConfig = new JwtConfig(); + + // 从系统配置表中读取配置 + jwtConfig.setJwtSecret(JwtConfigExpander.getJwtSecret()); + jwtConfig.setExpiredSeconds(JwtConfigExpander.getJwtTimeoutSeconds()); + + return new JwtTokenOperator(jwtConfig); + } + +} diff --git a/kernel-d-jwt/jwt-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-jwt/jwt-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..cf7f71dc0 --- /dev/null +++ b/kernel-d-jwt/jwt-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.jwt.starter.GunsJwtAutoConfiguration \ No newline at end of file diff --git a/kernel-d-jwt/pom.xml b/kernel-d-jwt/pom.xml new file mode 100644 index 000000000..b54526717 --- /dev/null +++ b/kernel-d-jwt/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-jwt + + pom + + + jwt-api + jwt-sdk + jwt-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-log/README.md b/kernel-d-log/README.md new file mode 100644 index 000000000..bc08bf198 --- /dev/null +++ b/kernel-d-log/README.md @@ -0,0 +1,5 @@ +日志模块 + +日志模块的存储介质可选,存本地,db,还是es都可以 + +日志模块的业务记录包含切controller,记录所有接口的操作记录 \ No newline at end of file diff --git a/kernel-d-log/log-api/README.md b/kernel-d-log/log-api/README.md new file mode 100644 index 000000000..6cee595aa --- /dev/null +++ b/kernel-d-log/log-api/README.md @@ -0,0 +1 @@ +日志模块的api \ No newline at end of file diff --git a/kernel-d-log/log-api/pom.xml b/kernel-d-log/log-api/pom.xml new file mode 100644 index 000000000..19a94c3ef --- /dev/null +++ b/kernel-d-log/log-api/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-log + 1.0.0 + ../pom.xml + + + log-api + + jar + + + + + + + + cn.stylefeng.roses + auth-api + 1.0.0 + true + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-api + 1.0.0 + + + + + + + javax.servlet + javax.servlet-api + true + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/LogManagerApi.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/LogManagerApi.java new file mode 100644 index 000000000..6b974b2ea --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/LogManagerApi.java @@ -0,0 +1,50 @@ +package cn.stylefeng.roses.kernel.log.api; + +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.log.api.pojo.manage.LogManagerParam; +import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO; + +import java.util.List; + +/** + * 日志管理相关的接口 + *

+ * 接口有多种实现,例如基于文件存储的日志,基于数据库存储的日志,基于es存储的日志 + * + * @author fengshuonan + * @date 2020/10/27 16:19 + */ +public interface LogManagerApi { + + /** + * 查询日志列表 + * + * @param logManagerParam 查询条件 + * @return 返回查询日志列表 + * @author fengshuonan + * @date 2020/10/28 11:27 + */ + List queryLogList(LogManagerParam logManagerParam); + + /** + * 查询日志列表 + * + * @param logManagerParam 查询条件 + * @return 返回查询日志列表分页结果 + * @author luojie + * @date 2020/11/3 10:40 + */ + PageResult queryLogListPage(LogManagerParam logManagerParam); + + /** + * 批量删除日志 + *

+ * 删除日志条件必须传入开始时间、结束时间、服务名称三个参数 + * + * @param logManagerParam 参数的封装 + * @author fengshuonan + * @date 2020/10/28 11:47 + */ + void deleteLogs(LogManagerParam logManagerParam); + +} \ No newline at end of file diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/LogRecordApi.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/LogRecordApi.java new file mode 100644 index 000000000..1756bc4a4 --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/LogRecordApi.java @@ -0,0 +1,42 @@ +package cn.stylefeng.roses.kernel.log.api; + +import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO; + +import java.util.List; + +/** + * 日志记录的api,只用于记录日志 + * + * @author fengshuonan + * @date 2020/10/27 16:19 + */ +public interface LogRecordApi { + + /** + * 同步记录日志,也就是记录成功才方法返回 + * + * @param logRecordDTO 日志记录的参数 + * @author fengshuonan + * @date 2020/10/27 17:38 + */ + void recordLog(LogRecordDTO logRecordDTO); + + /** + * 批量同步记录日志 + * + * @param logRecords 待输出日志列表 + * @author majianguo + * @date 2020/11/2 下午2:59 + */ + void recordLogByList(List logRecords); + + /** + * 异步记录日志,调用本方法直接返回结果,之后再异步记录日志 + * + * @param logRecordDTO 日志记录的参数 + * @author fengshuonan + * @date 2020/10/27 17:38 + */ + void recordLogAsync(LogRecordDTO logRecordDTO); + +} \ No newline at end of file diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/constants/LogConstants.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/constants/LogConstants.java new file mode 100644 index 000000000..adda76362 --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/constants/LogConstants.java @@ -0,0 +1,31 @@ +package cn.stylefeng.roses.kernel.log.api.constants; + +/** + * log模块的常量 + * + * @author fengshuonan + * @date 2020/10/27 15:56 + */ +public interface LogConstants { + + /** + * 日志模块的名称 + */ + String LOG_MODULE_NAME = "kernel-d-log"; + + /** + * 异常枚举的步进值 + */ + String LOG_EXCEPTION_STEP_CODE = "12"; + + /** + * 默认日志的名称 + */ + String LOG_DEFAULT_NAME = "API接口日志记录"; + + /** + * 默认日志服务名称 + */ + String LOG_DEFAULT_APP_NAME = "none-app-name"; + +} diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/constants/LogFileConstants.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/constants/LogFileConstants.java new file mode 100644 index 000000000..4c60a605d --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/constants/LogFileConstants.java @@ -0,0 +1,43 @@ +package cn.stylefeng.roses.kernel.log.api.constants; + +/** + * 日志文件的常量 + * + * @author fengshuonan + * @date 2020/10/28 15:56 + */ +public interface LogFileConstants { + + /** + * 记录日志文件名前缀,如果没有appName的话 + *

+ * 例如:app-logs-2020-10-28.log + */ + String DEFAULT_LOG_FILE_NAME = "app-logs"; + + /** + * 文件拼接符号 + */ + String FILE_CONTRACT_SYMBOL = "-"; + + /** + * 日志文件的后缀名 + */ + String FILE_SUFFIX = ".log"; + + /** + * 默认文件存储路径(windows的) + */ + String DEFAULT_FILE_SAVE_PATH_WINDOWS = "d:/logfiles"; + + /** + * 默认文件存储路径(linux和windows的的) + */ + String DEFAULT_FILE_SAVE_PATH_LINUX = "/tmp/logfiles"; + + /** + * 默认api日志记录的aop的顺序 + */ + Integer DEFAULT_API_LOG_AOP_SORT = 500; + +} diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/context/LogRecordContext.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/context/LogRecordContext.java new file mode 100644 index 000000000..ba1c8d2c7 --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/context/LogRecordContext.java @@ -0,0 +1,24 @@ +package cn.stylefeng.roses.kernel.log.api.context; + +import cn.hutool.extra.spring.SpringUtil; +import cn.stylefeng.roses.kernel.log.api.LogRecordApi; + +/** + * 日志操作api的获取 + * + * @author fengshuonan + * @date 2020/10/27 16:19 + */ +public class LogRecordContext { + + /** + * 获取日志操作api + * + * @author fengshuonan + * @date 2020/10/27 16:19 + */ + public static LogRecordApi me() { + return SpringUtil.getBean(LogRecordApi.class); + } + +} diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/context/ServerInfoContext.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/context/ServerInfoContext.java new file mode 100644 index 000000000..d71f7bed1 --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/context/ServerInfoContext.java @@ -0,0 +1,39 @@ +package cn.stylefeng.roses.kernel.log.api.context; + + +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.StrUtil; + +/** + * 临时缓存服务器信息 + * + * @author fengshuonan + * @date 2020/10/27 17:53 + */ +public class ServerInfoContext { + + /** + * 服务器IP + */ + private static String serverIp; + + /** + * 禁止new创建 + */ + private ServerInfoContext() { + } + + /** + * 获取server的ip + * + * @author fengshuonan + * @date 2020/10/27 17:56 + */ + public static String getServerIp() { + if (StrUtil.isEmpty(serverIp)) { + serverIp = NetUtil.getLocalhostStr(); + } + return serverIp; + } + +} diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/exception/LogException.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/exception/LogException.java new file mode 100644 index 000000000..85a08f443 --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/exception/LogException.java @@ -0,0 +1,19 @@ +package cn.stylefeng.roses.kernel.log.api.exception; + +import cn.stylefeng.roses.kernel.log.api.constants.LogConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; + +/** + * 日志异常枚举 + * + * @author fengshuonan + * @date 2020/10/15 15:59 + */ +public class LogException extends ServiceException { + + public LogException(AbstractExceptionEnum exception) { + super(LogConstants.LOG_MODULE_NAME, exception); + } + +} diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/exception/enums/LogExceptionEnum.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/exception/enums/LogExceptionEnum.java new file mode 100644 index 000000000..efd6f5293 --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/exception/enums/LogExceptionEnum.java @@ -0,0 +1,52 @@ +package cn.stylefeng.roses.kernel.log.api.exception.enums; + +import cn.stylefeng.roses.kernel.log.api.constants.LogConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import lombok.Getter; + +/** + * 日志异常枚举 + * + * @author fengshuonan + * @date 2020/10/27 16:18 + */ +@Getter +public enum LogExceptionEnum implements AbstractExceptionEnum { + + /** + * 查询或者删除日志时,传入的参数中没有app名称 + */ + APP_NAME_NOT_EXIST(RuleConstants.BUSINESS_ERROR_TYPE_CODE + LogConstants.LOG_EXCEPTION_STEP_CODE + "01", "应用名称不能为空!"), + + /** + * 查询或者删除日志时,传入的参数中没有查询时间 + */ + BEGIN_DATETIME_NOT_EXIST(RuleConstants.BUSINESS_ERROR_TYPE_CODE + LogConstants.LOG_EXCEPTION_STEP_CODE + "02", "开始时间不能为空,请填写精确到日的时间!"), + + /** + * 查询或者删除日志时,传入的参数中没有查询时间 + */ + END_DATETIME_NOT_EXIST(RuleConstants.BUSINESS_ERROR_TYPE_CODE + LogConstants.LOG_EXCEPTION_STEP_CODE + "03", "结束时间不能为空,请填写精确到日的时间!"), + + /** + * 初始化日志记录表失败,执行查询语句失败 + */ + LOG_SQL_EXE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + LogConstants.LOG_EXCEPTION_STEP_CODE + "04", "初始化日志记录表失败,执行查询语句失败"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + LogExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/expander/LogConfigExpander.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/expander/LogConfigExpander.java new file mode 100644 index 000000000..c58397a33 --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/expander/LogConfigExpander.java @@ -0,0 +1,48 @@ +package cn.stylefeng.roses.kernel.log.api.expander; + +import cn.stylefeng.roses.kernel.config.api.context.ConfigContext; +import cn.stylefeng.roses.kernel.log.api.constants.LogFileConstants; + +/** + * 日志记录相关的配置 + * + * @author fengshuonan + * @date 2020/10/28 16:11 + */ +public class LogConfigExpander { + + /** + * 获取日志记录的文件存储的位置(windows服务器) + *

+ * 末尾不带斜杠 + * + * @author fengshuonan + * @date 2020/10/28 16:14 + */ + public static String getLogFileSavePathWindows() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_LOG_FILE_SAVE_PATH_WINDOWS", String.class, LogFileConstants.DEFAULT_FILE_SAVE_PATH_WINDOWS); + } + + /** + * 获取日志记录的文件存储的位置(linux和mac服务器) + *

+ * 末尾不带斜杠 + * + * @author fengshuonan + * @date 2020/10/28 16:14 + */ + public static String getLogFileSavePathLinux() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_LOG_FILE_SAVE_PATH_LINUX", String.class, LogFileConstants.DEFAULT_FILE_SAVE_PATH_LINUX); + } + + /** + * 获取接口api的aop日志记录的aop的顺序 + * + * @author fengshuonan + * @date 2020/10/28 17:19 + */ + public static Integer getRequestApiLogAopSort() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_REQUEST_API_LOG_AOP_SORT", Integer.class, LogFileConstants.DEFAULT_API_LOG_AOP_SORT); + } + +} diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/LogRecordFactory.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/LogRecordFactory.java new file mode 100644 index 000000000..2c92366be --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/LogRecordFactory.java @@ -0,0 +1,53 @@ +package cn.stylefeng.roses.kernel.log.api.factory; + +import cn.hutool.extra.spring.SpringUtil; +import cn.stylefeng.roses.kernel.log.api.constants.LogConstants; +import cn.stylefeng.roses.kernel.log.api.context.ServerInfoContext; +import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO; + +import java.util.Date; + +/** + * 日志记录创建工厂,用来new LogRecordDTO,并填充一些信息 + *

+ * 一般用法为用本类去创建一个对象,再用其他的填充器去填充其他信息,例如认证,http接口信息等 + * + * @author fengshuonan + * @date 2020/10/28 17:28 + */ +public class LogRecordFactory { + + /** + * 创建日志记录 + * + * @author fengshuonan + * @date 2020/10/28 17:31 + */ + public static LogRecordDTO createLogRecord(String name, Object content) { + LogRecordDTO logRecordDTO = new LogRecordDTO(); + + // 设置日志名称 + logRecordDTO.setName(name); + + // 设置日志内容 + logRecordDTO.setContent(content); + + // 设置appName + String applicationName = null; + try { + applicationName = SpringUtil.getApplicationContext().getApplicationName(); + } catch (Exception e) { + applicationName = LogConstants.LOG_DEFAULT_APP_NAME; + } + logRecordDTO.setAppName(applicationName); + + // 设置当前时间 + logRecordDTO.setDateTime(new Date()); + + // 设置server ip + logRecordDTO.setServerIp(ServerInfoContext.getServerIp()); + + return logRecordDTO; + } + +} diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/appender/AuthedLogAppender.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/appender/AuthedLogAppender.java new file mode 100644 index 000000000..94596917b --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/appender/AuthedLogAppender.java @@ -0,0 +1,39 @@ +package cn.stylefeng.roses.kernel.log.api.factory.appender; + +import cn.stylefeng.roses.kernel.auth.api.context.LoginContext; +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; +import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO; + +/** + * 日志信息追加,用来追加用户的登录信息 + * + * @author fengshuonan + * @date 2020/10/27 17:45 + */ +public class AuthedLogAppender { + + /** + * 填充token和userId字段 + *

+ * 但是此方法会依赖auth-api模块,所以用这个方法得引入auth模块 + * + * @author fengshuonan + * @date 2020/10/27 18:22 + */ + public static void appendAuthedHttpLog(LogRecordDTO logRecordDTO) { + + // 填充当前登录的用户信息 + try { + // 填充登录用户的token + logRecordDTO.setToken(LoginContext.me().getToken()); + + // 填充登录用户的userId + LoginUser loginUser = LoginContext.me().getLoginUser(); + logRecordDTO.setUserId(loginUser.getId()); + } catch (Exception ignored) { + // 获取不到用户登录信息,就不填充 + } + + } + +} diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/appender/HttpLogAppender.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/appender/HttpLogAppender.java new file mode 100644 index 000000000..8d2a646d7 --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/appender/HttpLogAppender.java @@ -0,0 +1,62 @@ +package cn.stylefeng.roses.kernel.log.api.factory.appender; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.useragent.UserAgent; +import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO; +import cn.stylefeng.roses.kernel.rule.util.HttpServletUtil; + +import javax.servlet.http.HttpServletRequest; + +/** + * 日志信息追加,用来追加http接口请求信息 + * + * @author fengshuonan + * @date 2020/10/27 17:45 + */ +public class HttpLogAppender { + + /** + * 追加请求信息到logRecordDTO + * + * @author fengshuonan + * @date 2020/10/27 18:22 + */ + public static void appendHttpLog(LogRecordDTO logRecordDTO) { + + HttpServletRequest request; + try { + request = HttpServletUtil.getRequest(); + } catch (Exception e) { + // 如果不是http环境,则直接返回 + return; + } + + // 设置clientIp + logRecordDTO.setClientIp(HttpServletUtil.getRequestClientIp(request)); + + // 设置请求的url + logRecordDTO.setUrl(request.getServletPath()); + + // 设置http的请求方法 + logRecordDTO.setHttpMethod(request.getMethod()); + + // 解析http头,获取userAgent信息 + UserAgent userAgent = HttpServletUtil.getUserAgent(request); + + if (userAgent == null) { + return; + } + + // 设置浏览器标识 + if (ObjectUtil.isNotEmpty(userAgent.getBrowser())) { + logRecordDTO.setBrowser(userAgent.getBrowser().getName()); + } + + // 设置浏览器操作系统 + if (ObjectUtil.isNotEmpty(userAgent.getOs())) { + logRecordDTO.setOs(userAgent.getOs().getName()); + } + + } + +} diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/appender/ParamsLogAppender.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/appender/ParamsLogAppender.java new file mode 100644 index 000000000..b2f49448d --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/factory/appender/ParamsLogAppender.java @@ -0,0 +1,31 @@ +package cn.stylefeng.roses.kernel.log.api.factory.appender; + +import com.alibaba.fastjson.JSON; +import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO; + +import java.util.Map; + +/** + * 日志信息追加,用来追加方法的参数信息 + * + * @author fengshuonan + * @date 2020/10/28 17:48 + */ +public class ParamsLogAppender { + + /** + * 参数信息追加 + * + * @author fengshuonan + * @date 2020/10/28 17:48 + */ + public static void appendAuthedHttpLog(LogRecordDTO logRecordDTO, Map requestParam, Object response) { + + // 追加请求参数信息 + logRecordDTO.setRequestParams(JSON.toJSONString(requestParam)); + + // 追加相应参数信息 + logRecordDTO.setRequestResult(JSON.toJSONString(response)); + } + +} diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/pojo/manage/LogManagerParam.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/pojo/manage/LogManagerParam.java new file mode 100644 index 000000000..32556ca65 --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/pojo/manage/LogManagerParam.java @@ -0,0 +1,82 @@ +package cn.stylefeng.roses.kernel.log.api.pojo.manage; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; + +/** + * 日志管理的查询参数 + * + * @author fengshuonan + * @date 2020/10/28 11:26 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class LogManagerParam extends BaseRequest { + + /** + * 单条日志id + */ + private Long logId; + + /** + * 查询的起始时间 + */ + @NotBlank(message = "起始时间不能为空,请检查beginDateTime参数", groups = {delete.class}) + private String beginDateTime; + + /** + * 查询日志的结束时间 + */ + @NotBlank(message = "结束时间不能为空,请检查endDateTime参数", groups = {delete.class}) + private String endDateTime; + + /** + * 第几页,从1开始 + */ + private Integer pageNo; + + /** + * 每页大小 + */ + private Integer pageSize; + + /** + * 日志的名称,一般为业务名称 + */ + private String name; + + /** + * 服务名称,一般为spring.application.name + */ + @NotBlank(message = "服务名称不能为空,请检查appName参数", groups = {delete.class}) + private String appName; + + /** + * 当前服务器的ip + */ + private String serverIp; + + /** + * 客户端请求的token + */ + private String token; + + /** + * 客户端请求的用户id + */ + private Long userId; + + /** + * 客户端的ip + */ + private String clientIp; + + /** + * 当前用户请求的url + */ + private String url; + +} diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/pojo/record/LogRecordDTO.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/pojo/record/LogRecordDTO.java new file mode 100644 index 000000000..502117d09 --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/pojo/record/LogRecordDTO.java @@ -0,0 +1,100 @@ +package cn.stylefeng.roses.kernel.log.api.pojo.record; + +import lombok.Data; + +import java.util.Date; + +/** + * 日志记录需要的参数 + * + * @author fengshuonan + * @date 2020/10/27 16:31 + */ +@Data +public class LogRecordDTO { + + /** + * 日志的名称,一般为业务名称 + */ + private String name; + + /** + * 日志记录的内容 + */ + private Object content; + + /** + * 服务名称,一般为spring.application.name + */ + private String appName; + + /** + * http或方法的请求参数体 + */ + private String requestParams; + + /** + * http或方法的请求结果 + */ + private String requestResult; + + /** + * 操作发生的时间 + */ + private Date dateTime; + + /** + * 当前服务器的ip + */ + private String serverIp; + + /** + * 客户端请求的token + *

+ * 如果是http请求,并且用户已经登录,可以带这项 + */ + private String token; + + /** + * 客户端请求的用户id + *

+ * 如果是http请求,并且用户已经登录,可以带这项 + */ + private Long userId; + + /** + * 客户端的ip + *

+ * 如果是http请求,可以带这项 + */ + private String clientIp; + + /** + * 当前用户请求的url + *

+ * 如果是http请求,可以带这项 + */ + private String url; + + /** + * 请求方式(GET POST PUT DELETE) + *

+ * 如果是http请求,可以带这项 + */ + private String httpMethod; + + /** + * 浏览器 + *

+ * 如果是http请求,可以带这项 + */ + private String browser; + + /** + * 操作系统 + *

+ * 如果是http请求,可以带这项 + */ + private String os; + +} diff --git a/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/threadpool/LogManagerThreadPool.java b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/threadpool/LogManagerThreadPool.java new file mode 100644 index 000000000..bbd1b5013 --- /dev/null +++ b/kernel-d-log/log-api/src/main/java/cn/stylefeng/roses/kernel/log/api/threadpool/LogManagerThreadPool.java @@ -0,0 +1,42 @@ +package cn.stylefeng.roses.kernel.log.api.threadpool; + +import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean; + +import java.util.TimerTask; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + + +/** + * 异步记录日志用的线程池 + * + * @author fengshuonan + * @date 2020/10/28 15:24 + */ +public class LogManagerThreadPool { + + /** + * 异步操作记录日志的线程池 + */ + private final ScheduledThreadPoolExecutor schedule; + + public LogManagerThreadPool() { + schedule = new ScheduledThreadPoolExecutor(10, new ScheduledExecutorFactoryBean()); + } + + public LogManagerThreadPool(int poolSize) { + schedule = new ScheduledThreadPoolExecutor(poolSize, new ScheduledExecutorFactoryBean()); + } + + /** + * 异步执行日志的方法 + * + * @author fengshuonan + * @date 2020/10/28 15:27 + */ + public void executeLog(TimerTask task) { + int operateDelayTime = 10; + schedule.schedule(task, operateDelayTime, TimeUnit.MILLISECONDS); + } + +} diff --git a/kernel-d-log/log-business-manage/README.md b/kernel-d-log/log-business-manage/README.md new file mode 100644 index 000000000..78b25acd1 --- /dev/null +++ b/kernel-d-log/log-business-manage/README.md @@ -0,0 +1 @@ +日志业务模块,包含在线管理日志的业务 \ No newline at end of file diff --git a/kernel-d-log/log-business-manage/pom.xml b/kernel-d-log/log-business-manage/pom.xml new file mode 100644 index 000000000..e80709c5b --- /dev/null +++ b/kernel-d-log/log-business-manage/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-log + 1.0.0 + ../pom.xml + + + log-business-manage + + jar + + + + + + cn.stylefeng.roses + log-api + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-d-log/log-business-manage/src/main/java/cn/stylefeng/roses/kernel/log/modular/manage/controller/LogManagerController.java b/kernel-d-log/log-business-manage/src/main/java/cn/stylefeng/roses/kernel/log/modular/manage/controller/LogManagerController.java new file mode 100644 index 000000000..f9d32d2a0 --- /dev/null +++ b/kernel-d-log/log-business-manage/src/main/java/cn/stylefeng/roses/kernel/log/modular/manage/controller/LogManagerController.java @@ -0,0 +1,56 @@ +package cn.stylefeng.roses.kernel.log.modular.manage.controller; + +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.log.api.LogManagerApi; +import cn.stylefeng.roses.kernel.log.api.pojo.manage.LogManagerParam; +import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO; +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * 日志管理控制器 + * + * @author luojie + * @date 2020/11/3 12:44 + */ +@RestController +@ApiResource(name = "日志管理控制器") +public class LogManagerController { + + /** + * 日志管理api + */ + @Autowired + private LogManagerApi logManagerApi; + + /** + * 查询日志列表 + * + * @author luojie + * @date 2020/11/3 12:58 + */ + @PostResource(name = "查询日志列表", path = "/logManager/list") + public ResponseData list(@RequestBody LogManagerParam logManagerParam) { + PageResult logRecordDtoPageResult = logManagerApi.queryLogListPage(logManagerParam); + return new SuccessResponseData(logRecordDtoPageResult); + } + + /** + * 删除日志 + * + * @author luojie + * @date 2020/11/3 13:47 + */ + @PostResource(name = "删除日志", path = "/logManager/delete") + public ResponseData delete(@RequestBody @Validated(LogManagerParam.delete.class) LogManagerParam logManagerParam) { + logManagerApi.deleteLogs(logManagerParam); + return new SuccessResponseData(); + } + +} diff --git a/kernel-d-log/log-business-requestapi/README.md b/kernel-d-log/log-business-requestapi/README.md new file mode 100644 index 000000000..12f60161d --- /dev/null +++ b/kernel-d-log/log-business-requestapi/README.md @@ -0,0 +1,3 @@ +日志业务模块,包含对每个api接口的日志记录 + +对控制器层接口的aop操作 \ No newline at end of file diff --git a/kernel-d-log/log-business-requestapi/pom.xml b/kernel-d-log/log-business-requestapi/pom.xml new file mode 100644 index 000000000..e40b3ac99 --- /dev/null +++ b/kernel-d-log/log-business-requestapi/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-log + 1.0.0 + ../pom.xml + + + log-business-requestapi + + jar + + + + + + cn.stylefeng.roses + log-api + 1.0.0 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + diff --git a/kernel-d-log/log-business-requestapi/src/main/java/cn/stylefeng/roses/kernel/log/modular/requestapi/aop/RequestApiLogRecordAop.java b/kernel-d-log/log-business-requestapi/src/main/java/cn/stylefeng/roses/kernel/log/modular/requestapi/aop/RequestApiLogRecordAop.java new file mode 100644 index 000000000..837cecf31 --- /dev/null +++ b/kernel-d-log/log-business-requestapi/src/main/java/cn/stylefeng/roses/kernel/log/modular/requestapi/aop/RequestApiLogRecordAop.java @@ -0,0 +1,159 @@ +package cn.stylefeng.roses.kernel.log.modular.requestapi.aop; + +import cn.stylefeng.roses.kernel.log.api.LogRecordApi; +import cn.stylefeng.roses.kernel.log.api.constants.LogConstants; +import cn.stylefeng.roses.kernel.log.api.expander.LogConfigExpander; +import cn.stylefeng.roses.kernel.log.api.factory.LogRecordFactory; +import cn.stylefeng.roses.kernel.log.api.factory.appender.AuthedLogAppender; +import cn.stylefeng.roses.kernel.log.api.factory.appender.HttpLogAppender; +import cn.stylefeng.roses.kernel.log.api.factory.appender.ParamsLogAppender; +import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO; +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.annotation.Pointcut; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.Ordered; +import org.springframework.core.ParameterNameDiscoverer; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +/** + * 每个请求接口记录日志的AOP + *

+ * 将控制器controller包下的所有控制器类,执行的时候对url,参数,结果等进行记录 + * + * @author fengshuonan + * @date 2020/10/28 17:06 + */ +@Aspect +@Slf4j +public class RequestApiLogRecordAop implements Ordered { + + /** + * 日志记录的api + */ + private final LogRecordApi logRecordApi; + + public RequestApiLogRecordAop(LogRecordApi logRecordApi) { + this.logRecordApi = logRecordApi; + } + + /** + * 切所有controller包 + */ + @Pointcut("execution(* *..controller.*.*(..))") + public void cutService() { + + } + + @Around("cutService()") + public Object around(ProceedingJoinPoint point) throws Throwable { + Object result = point.proceed(); + + try { + Map args = getFieldsName(point); + recordLog(args, result); + } catch (Exception e) { + log.error("日志记录没有记录成功!", e); + } + + return result; + } + + /** + * 将请求方法记录日志的过程 + * + * @param params AOP拦截方法的参数封装,key是参数名称,v是参数值 + * @param result AOP拦截方法的返回值 + * @author fengshuonan + * @date 2020/10/28 17:38 + */ + private void recordLog(Map params, Object result) { + + // 创建日志对象 + LogRecordDTO logRecordDTO = LogRecordFactory.createLogRecord(LogConstants.LOG_DEFAULT_NAME, null); + + // 填充用户登录信息 + AuthedLogAppender.appendAuthedHttpLog(logRecordDTO); + + // 填充http接口请求信息 + HttpLogAppender.appendHttpLog(logRecordDTO); + + // 追加参数信息 + ParamsLogAppender.appendAuthedHttpLog(logRecordDTO, params, result); + + // 异步记录日志 + logRecordApi.recordLogAsync(logRecordDTO); + } + + @Override + public int getOrder() { + return LogConfigExpander.getRequestApiLogAopSort(); + } + + /** + * AOP获取参数名称和参数值 + * + * @param joinPoint joinPoint对象 + * @return 返回K, V格式的参数,key是参数名称,v是参数值 + * @author majianguo + * @date 2020/11/2 10:40 + */ + private Map getFieldsName(ProceedingJoinPoint joinPoint) { + + // 获取被拦截方法的所有参数 + Object[] args = joinPoint.getArgs(); + + // 通过map封装参数和参数值,key参数名,value是参数值 + Map paramMap = new HashMap<>(args.length); + try { + String classType = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + + // 处理基本类型 + Class[] classes = new Class[args.length]; + for (int k = 0; k < args.length; k++) { + classes[k] = args[k].getClass(); + } + + ParameterNameDiscoverer defaultParameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + + // 获取指定的方法,第二个参数可以不传,但是为了防止有重载的现象,还是需要传入参数的类型 + Method method = Class.forName(classType).getMethod(methodName, classes); + + // 参数名 + String[] parameterNames = defaultParameterNameDiscoverer.getParameterNames(method); + + // 为空直接返回 + if (null == parameterNames) { + return new HashMap<>(1); + } + + // 装载参数名称和参数值 + for (int i = 0; i < parameterNames.length; i++) { + paramMap.put(parameterNames[i], args[i]); + } + + } catch (Exception e) { + + // 打印日志 + if (log.isDebugEnabled()) { + e.printStackTrace(); + } + log.error(e.getMessage()); + + // 有异常则不显示参数名称直接返回参数 + for (int i = 0; i < args.length; i++) { + paramMap.put("args" + i, args[i]); + } + + } + + return paramMap; + } + +} diff --git a/kernel-d-log/log-sdk-db/README.md b/kernel-d-log/log-sdk-db/README.md new file mode 100644 index 000000000..66601bf27 --- /dev/null +++ b/kernel-d-log/log-sdk-db/README.md @@ -0,0 +1 @@ +日志记录的sdk,用于将日志记录到数据库中,提供相关日志管理接口 \ No newline at end of file diff --git a/kernel-d-log/log-sdk-db/pom.xml b/kernel-d-log/log-sdk-db/pom.xml new file mode 100644 index 000000000..54755ccd1 --- /dev/null +++ b/kernel-d-log/log-sdk-db/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-log + 1.0.0 + ../pom.xml + + + log-sdk-db + + jar + + + + + + cn.stylefeng.roses + log-api + 1.0.0 + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + diff --git a/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/DbLogManagerServiceImpl.java b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/DbLogManagerServiceImpl.java new file mode 100644 index 000000000..998e52bb6 --- /dev/null +++ b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/DbLogManagerServiceImpl.java @@ -0,0 +1,200 @@ +package cn.stylefeng.roses.kernel.log.db; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.log.db.entity.SysLog; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.log.api.LogManagerApi; +import cn.stylefeng.roses.kernel.log.api.pojo.manage.LogManagerParam; +import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO; +import cn.stylefeng.roses.kernel.log.db.service.SysLogService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 日志管理,数据库实现 + * + * @author luojie + * @date 2020/11/2 17:40 + */ +@Slf4j +@Service +public class DbLogManagerServiceImpl implements LogManagerApi { + + /** + * 日志记录 service接口 + */ + @Resource + private SysLogService sysLogService; + + @Override + public List queryLogList(LogManagerParam logManagerParam) { + PageResult logRecordDtoPageResult = queryLogListPage(logManagerParam); + return logRecordDtoPageResult.getRows(); + } + + @Override + public PageResult queryLogListPage(LogManagerParam logManagerParam) { + if (ObjectUtil.isEmpty(logManagerParam)) { + return new PageResult<>(); + } + + // 创建默认的请求方法 + createDefaultLogManagerParam(logManagerParam); + + LambdaQueryWrapper sysLogLambdaQueryWrapper = new LambdaQueryWrapper<>(); + + // 创建查询条件 + createQueryCondition(logManagerParam, sysLogLambdaQueryWrapper); + + // 查询分页结果 + Page sysLogPage = new Page<>(logManagerParam.getPageNo(), logManagerParam.getPageSize()); + Page page = sysLogService.page(sysLogPage, sysLogLambdaQueryWrapper); + + PageResult pageResult = PageResultFactory.createPageResult(page); + PageResult logRecordDtoPageResult = new PageResult<>(); + BeanUtil.copyProperties(pageResult, logRecordDtoPageResult); + + return logRecordDtoPageResult; + } + + @Transactional(rollbackFor = {Exception.class}) + @Override + public void deleteLogs(LogManagerParam logManagerParam) { + if (ObjectUtil.isEmpty(logManagerParam)) { + return; + } + + // 获取参数 + String beginDateTime = logManagerParam.getBeginDateTime(); + String endDateTime = logManagerParam.getEndDateTime(); + String appName = logManagerParam.getAppName(); + + LogManagerParam param = new LogManagerParam(); + param.setBeginDateTime(beginDateTime); + param.setEndDateTime(endDateTime); + param.setAppName(appName); + + LambdaQueryWrapper sysLogLambdaQueryWrapper = new LambdaQueryWrapper<>(); + + // 创建查询条件 + createQueryCondition(param, sysLogLambdaQueryWrapper); + + // 删除指定条件日志 + sysLogService.remove(sysLogLambdaQueryWrapper); + } + + /** + * 创建查询条件 + * + * @param logManagerParam 日志管理的查询参数 + * @param sysLogLambdaQueryWrapper mp的查询条件 + * @author luojie + * @date 2020/11/3 11:22 + */ + private void createQueryCondition(LogManagerParam logManagerParam, LambdaQueryWrapper sysLogLambdaQueryWrapper) { + // 设置查询条件的起始时间和结束时间 + sysLogLambdaQueryWrapper.between(SysLog::getDateTime, logManagerParam.getBeginDateTime(), logManagerParam.getEndDateTime()); + + // 根据日志名称查询 + String name = logManagerParam.getName(); + if (StrUtil.isNotEmpty(name)) { + sysLogLambdaQueryWrapper.and(q -> { + q.eq(SysLog::getName, name); + }); + } + + // 根据服务名称查询 + String appName = logManagerParam.getAppName(); + if (StrUtil.isNotEmpty(appName)) { + sysLogLambdaQueryWrapper.and(q -> { + q.eq(SysLog::getAppName, appName); + }); + } + + // 根据服务端ip查询 + String serverIp = logManagerParam.getServerIp(); + if (StrUtil.isNotEmpty(serverIp)) { + sysLogLambdaQueryWrapper.and(q -> { + q.eq(SysLog::getServerIp, serverIp); + }); + } + + // 根据客户端请求的token查询 + String token = logManagerParam.getToken(); + if (StrUtil.isNotEmpty(token)) { + sysLogLambdaQueryWrapper.and(q -> { + q.eq(SysLog::getToken, token); + }); + } + + // 根据客户端请求的用户id查询 + Long userId = logManagerParam.getUserId(); + if (userId != null) { + sysLogLambdaQueryWrapper.and(q -> { + q.eq(SysLog::getUserId, userId); + }); + } + + // 根据客户端的ip查询 + String clientIp = logManagerParam.getClientIp(); + if (StrUtil.isNotEmpty(clientIp)) { + sysLogLambdaQueryWrapper.and(q -> { + q.eq(SysLog::getClientIp, clientIp); + }); + } + + // 根据当前用户请求的url查询 + String url = logManagerParam.getUrl(); + if (StrUtil.isNotEmpty(clientIp)) { + sysLogLambdaQueryWrapper.and(q -> { + q.like(SysLog::getUrl, url); + }); + } + + // 根据时间倒序排序 + sysLogLambdaQueryWrapper.orderByDesc(SysLog::getDateTime); + } + + /** + * 创建默认的请求方法 + * + * @param logManagerParam 日志管理的查询参数 + * @author luojie + * @date 2020/11/3 11:20 + */ + private void createDefaultLogManagerParam(LogManagerParam logManagerParam) { + // 默认从第一页开始 + if (logManagerParam.getPageNo() == null) { + logManagerParam.setPageNo(1); + } + + // 默认每页10条 + if (logManagerParam.getPageSize() == null) { + logManagerParam.setPageSize(10); + } + + // 开始时间为空则用当天时间开始时间 + if (StrUtil.isEmpty(logManagerParam.getBeginDateTime())) { + String beginDateTime = DateUtil.beginOfDay(DateUtil.date()).toString(DatePattern.NORM_DATETIME_FORMAT); + logManagerParam.setBeginDateTime(beginDateTime); + } + + // 结束时间为空则默认用当前时间加一天 + if (StrUtil.isEmpty(logManagerParam.getEndDateTime())) { + String endDateTime = DateUtil.beginOfDay(DateUtil.tomorrow()).toString(DatePattern.NORM_DATETIME_FORMAT); + logManagerParam.setEndDateTime(endDateTime); + } + } + +} diff --git a/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/DbLogRecordServiceImpl.java b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/DbLogRecordServiceImpl.java new file mode 100644 index 000000000..5c2e77f85 --- /dev/null +++ b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/DbLogRecordServiceImpl.java @@ -0,0 +1,283 @@ +package cn.stylefeng.roses.kernel.log.db; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.log.Log; +import cn.hutool.log.LogFactory; +import cn.stylefeng.roses.kernel.log.api.LogRecordApi; +import cn.stylefeng.roses.kernel.log.api.constants.LogConstants; +import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO; +import cn.stylefeng.roses.kernel.log.api.threadpool.LogManagerThreadPool; +import cn.stylefeng.roses.kernel.log.db.entity.SysLog; +import cn.stylefeng.roses.kernel.log.db.service.SysLogService; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +/** + * 数据库存储方式的日志记录器 todo 有问题 + * + * @author luojie + * @date 2020/11/2 15:50 + */ +@Service +public class DbLogRecordServiceImpl implements LogRecordApi { + + /** + * Hutool日志对象 + */ + private final Log log = LogFactory.get(); + + /** + * 日志记录 service接口 + */ + private final SysLogService sysLogService; + + /** + * 异步记录日志用的线程池 + */ + private final LogManagerThreadPool logManagerThreadPool; + + /** + * 日志刷新管理器 + */ + private final LogRefreshManager logRefreshManager; + + public DbLogRecordServiceImpl(LogManagerThreadPool logManagerThreadPool, SysLogService sysLogService) { + this.logManagerThreadPool = logManagerThreadPool; + this.sysLogService = sysLogService; + this.logRefreshManager = new LogRefreshManager(); + this.logRefreshManager.start(); + } + + public DbLogRecordServiceImpl(LogManagerThreadPool logManagerThreadPool, SysLogService sysLogService, long sleepTime, int maxCount) { + this.logManagerThreadPool = logManagerThreadPool; + this.sysLogService = sysLogService; + this.logRefreshManager = new LogRefreshManager(sleepTime, maxCount); + this.logRefreshManager.start(); + } + + @Override + public void recordLog(LogRecordDTO logRecordDTO) { + if (logRecordDTO == null) { + return; + } + + // 输出日志 + recordLogByList(CollectionUtil.list(false, logRecordDTO)); + } + + @Override + public void recordLogByList(List logRecords) { + + if (ObjectUtil.isEmpty(logRecords)) { + return; + } + + List sysLogList = logRecords.stream().map(logRecordDTO -> { + SysLog sysLog = new SysLog(); + // 复制logRecordDTO对象属性到sysLog + BeanUtil.copyProperties(logRecordDTO, sysLog); + + // 日志名称为空的话则获取默认日志名称 + if (StrUtil.isEmpty(sysLog.getName())) { + sysLog.setName(LogConstants.LOG_DEFAULT_NAME); + } + + // 服务名称为空的话则获取默认服务名称 + if (StrUtil.isEmpty(sysLog.getAppName())) { + sysLog.setAppName(LogConstants.LOG_DEFAULT_APP_NAME); + } + + // 如果操作时间为空的话插入当前时间 + if (sysLog.getDateTime() == null) { + sysLog.setDateTime(new Date()); + } + return sysLog; + }).collect(Collectors.toList()); + + // 插入到数据库 + sysLogService.saveBatch(sysLogList); + } + + @Override + public void recordLogAsync(LogRecordDTO logRecordDTO) { + logManagerThreadPool.executeLog(new TimerTask() { + @Override + public void run() { + logRefreshManager.putLog(logRecordDTO); + } + }); + } + + /** + * 日志刷新管理器 + *

+ * 该类暂存所有将要写出到磁盘的日志,使用内存缓冲区减少对磁盘IO的操作 + *

+ * 该类维护一个最大日志数和一个刷新日志间隔,满足任意一个条件即可触发从内存写出日志到磁盘的操作 + * + * @author majianguo + * @date 2020/10/31 15:05 + */ + class LogRefreshManager extends Thread { + + /** + * 刷新日志间隔(默认3秒),单位毫秒 + */ + private final long sleepTime; + + /** + * 满足多少条就强制刷新一次(默认300条),该值只是一个大约值,实际记录并不会一定等于该值(可能会大于该值,不可能小于该值) + */ + private final int maxCount; + + /** + * 刷新数据时间标志,每次刷新都记录当前的时间戳,方便定时刷新准确判断上次刷新和本次刷新的时间间隔 + */ + private final AtomicLong refreshMark = new AtomicLong(); + + public LogRefreshManager() { + this.sleepTime = 3000; + this.maxCount = 300; + } + + public LogRefreshManager(long sleepTime) { + this.sleepTime = sleepTime; + this.maxCount = 300; + } + + public LogRefreshManager(int maxCount) { + this.sleepTime = 3000; + this.maxCount = maxCount; + } + + public LogRefreshManager(long sleepTime, int maxCount) { + this.sleepTime = sleepTime; + this.maxCount = maxCount; + } + + /** + * 未处理日志的消息队列 + */ + private final Queue queue = new ConcurrentLinkedQueue<>(); + + /** + * 消息总数,队列的size方法会遍历一遍队列,所以自己维护大小 + */ + public AtomicInteger count = new AtomicInteger(0); + + /** + * 往队列内新增一条日志数据 + * + * @param logRecordDTO 日志对象 + * @author majianguo + * @date 2020/10/31 14:59 + */ + public void putLog(LogRecordDTO logRecordDTO) { + + int queueDataCount = count.get(); + + // 如果是第一条消息,刷新一次refreshMark + if (queueDataCount == 0) { + refreshMark.getAndSet(System.currentTimeMillis()); + } + + // 如果后续写入磁盘的操作有异常(磁盘满了),为了防止日志刷不出去导致OOM,内存中最大只保留maxCount数一倍的日志,后续日志将丢弃 + if (queueDataCount >= (maxCount * 2)) { + return; + } + + queue.offer(logRecordDTO); + count.incrementAndGet(); + } + + /** + * 刷新日志到磁盘的操作 + * + * @author majianguo + * @date 2020/10/31 15:48 + */ + private void refresh() { + + // 让睡眠线程本次不要再调本方法,睡眠至下次看refreshMark的值再决定要不要调用本方法 + refreshMark.getAndSet(System.currentTimeMillis()); + + // 获取总数 + int num = count.getAndSet(0); + + // 再队列中读取num条数据,放入list + List cacheAll = new ArrayList<>(num); + for (int i = 0; i < num; i++) { + LogRecordDTO item = queue.poll(); + if (null == item) { + break; + } + cacheAll.add(item); + } + + // 调用方法刷新到磁盘 + recordLogByList(cacheAll); + } + + /** + * 日志数据定时执行器 + *

+ * 用于定时检测日志数据是否可以写入数据 + * + * @author majianguo + * @date 2020/10/31 15:57 + */ + private void timing() { + long currentTimeMillis = System.currentTimeMillis(); + + // 如果是激活状态,且消息数大于零,且符合上次调用refresh方法到目前时间的间隔,那就调用一次refresh方法 + if ((refreshMark.get() + sleepTime) <= currentTimeMillis && count.get() > 0) { + refresh(); + } + } + + /** + * 日志数据监听器 + *

+ * 用于监听日志消息队列,达到设定的数就开始执行刷入硬盘的操作 + * + * @author majianguo + * @date 2020/11/2 9:32 + */ + private void listener() { + // 判断如果队列里面的数据大于等于设定的最大消息数,就调用refresh方法刷新一次数据 + if (count.get() >= maxCount) { + refresh(); + } + } + + @Override + @SuppressWarnings("InfiniteLoopStatement") + public void run() { + try { + for (; ; ) { + // 消息监听器 + listener(); + // 定时任务监听器 + timing(); + TimeUnit.MILLISECONDS.sleep(10); + } + } catch (InterruptedException e) { + + if (log.isDebugEnabled()) { + e.printStackTrace(); + } + log.error(e.getMessage()); + } + } + } + +} diff --git a/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/entity/SysLog.java b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/entity/SysLog.java new file mode 100644 index 000000000..9ddca319d --- /dev/null +++ b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/entity/SysLog.java @@ -0,0 +1,114 @@ +package cn.stylefeng.roses.kernel.log.db.entity; + +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 cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + * 日志记录 + * + * @author luojie + * @date 2020/11/2 15:59 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_log") +public class SysLog extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 日志的名称,一般为业务名称 + */ + @TableField(value = "name") + private String name; + + /** + * 日志记录的内容 + */ + @TableField(value = "content") + private String content; + + /** + * 服务名称,一般为spring.application.name + */ + @TableField(value = "app_name") + private String appName; + + /** + * http或方法的请求参数体 + */ + @TableField(value = "request_params") + private String requestParams; + + /** + * http或方法的请求结果 + */ + @TableField(value = "request_result") + private String requestResult; + + /** + * 操作发生的时间 + */ + @TableField(value = "date_time") + private Date dateTime; + + /** + * 当前服务器的ip + */ + @TableField(value = "server_ip") + private String serverIp; + + /** + * 客户端请求的token,如果是http请求,并且用户已经登录 + */ + @TableField(value = "token") + private String token; + + /** + * 客户端请求的用户id,如果是http请求,并且用户已经登录 + */ + @TableField(value = "user_id") + private Long userId; + + /** + * 客户端的ip + */ + @TableField(value = "client_ip") + private String clientIp; + + /** + * 当前用户请求的url + */ + @TableField(value = "url") + private String url; + + /** + * 请求方式(GET POST PUT DELETE) + */ + @TableField(value = "http_method") + private String httpMethod; + + /** + * 浏览器,如果是http请求 + */ + @TableField(value = "browser") + private String browser; + + /** + * 操作系统,如果是http请求 + */ + @TableField(value = "os") + private String os; + +} \ No newline at end of file diff --git a/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/mapper/SysLogMapper.java b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/mapper/SysLogMapper.java new file mode 100644 index 000000000..39a1651ff --- /dev/null +++ b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/mapper/SysLogMapper.java @@ -0,0 +1,14 @@ +package cn.stylefeng.roses.kernel.log.db.mapper; + +import cn.stylefeng.roses.kernel.log.db.entity.SysLog; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 日志记录 Mapper 接口 + * + * @author luojie + * @date 2020/11/2 16:22 + */ +public interface SysLogMapper extends BaseMapper { + +} diff --git a/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/mapper/mapping/SysLogMapper.xml b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/mapper/mapping/SysLogMapper.xml new file mode 100644 index 000000000..fa32569af --- /dev/null +++ b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/mapper/mapping/SysLogMapper.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/service/SysLogService.java b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/service/SysLogService.java new file mode 100644 index 000000000..0c0215f57 --- /dev/null +++ b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/service/SysLogService.java @@ -0,0 +1,14 @@ +package cn.stylefeng.roses.kernel.log.db.service; + +import cn.stylefeng.roses.kernel.log.db.entity.SysLog; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + * 日志记录 service接口 + * + * @author luojie + * @date 2020/11/2 17:44 + */ +public interface SysLogService extends IService { + +} diff --git a/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/service/impl/SysLogServiceImpl.java b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/service/impl/SysLogServiceImpl.java new file mode 100644 index 000000000..1eaace865 --- /dev/null +++ b/kernel-d-log/log-sdk-db/src/main/java/cn/stylefeng/roses/kernel/log/db/service/impl/SysLogServiceImpl.java @@ -0,0 +1,18 @@ +package cn.stylefeng.roses.kernel.log.db.service.impl; + +import cn.stylefeng.roses.kernel.log.db.entity.SysLog; +import cn.stylefeng.roses.kernel.log.db.mapper.SysLogMapper; +import cn.stylefeng.roses.kernel.log.db.service.SysLogService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * 日志记录 service接口实现类 + * + * @author luojie + * @date 2020/11/2 17:45 + */ +@Service +public class SysLogServiceImpl extends ServiceImpl implements SysLogService { + +} diff --git a/kernel-d-log/log-sdk-file/README.md b/kernel-d-log/log-sdk-file/README.md new file mode 100644 index 000000000..e323d9802 --- /dev/null +++ b/kernel-d-log/log-sdk-file/README.md @@ -0,0 +1 @@ +日志记录的sdk,用于将日志记录到文件中,提供相关日志管理接口 \ No newline at end of file diff --git a/kernel-d-log/log-sdk-file/pom.xml b/kernel-d-log/log-sdk-file/pom.xml new file mode 100644 index 000000000..6bf544e4f --- /dev/null +++ b/kernel-d-log/log-sdk-file/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-log + 1.0.0 + ../pom.xml + + + log-sdk-file + + jar + + + + + + cn.stylefeng.roses + log-api + 1.0.0 + + + + + + cn.stylefeng.roses + auth-api + 1.0.0 + + + + + + cn.stylefeng.roses + config-api + 1.0.0 + + + + + diff --git a/kernel-d-log/log-sdk-file/src/main/java/cn/stylefeng/roses/kernel/log/file/FileLogManagerServiceImpl.java b/kernel-d-log/log-sdk-file/src/main/java/cn/stylefeng/roses/kernel/log/file/FileLogManagerServiceImpl.java new file mode 100644 index 000000000..e858bda90 --- /dev/null +++ b/kernel-d-log/log-sdk-file/src/main/java/cn/stylefeng/roses/kernel/log/file/FileLogManagerServiceImpl.java @@ -0,0 +1,275 @@ +package cn.stylefeng.roses.kernel.log.file; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSON; +import cn.stylefeng.roses.kernel.auth.api.context.LoginContext; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.log.api.LogManagerApi; +import cn.stylefeng.roses.kernel.log.api.exception.LogException; +import cn.stylefeng.roses.kernel.log.api.pojo.manage.LogManagerParam; +import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.RandomAccessFile; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import static cn.stylefeng.roses.kernel.log.api.constants.LogFileConstants.FILE_CONTRACT_SYMBOL; +import static cn.stylefeng.roses.kernel.log.api.constants.LogFileConstants.FILE_SUFFIX; +import static cn.stylefeng.roses.kernel.log.api.exception.enums.LogExceptionEnum.*; + +/** + * 文件日志读取管理实现类 + * + * @author majianguo + * @date 2020/11/3 上午10:56 + */ +@Slf4j +public class FileLogManagerServiceImpl implements LogManagerApi { + + private final String fileSavePath; + + /** + * 构造函数 + * + * @param fileSavePath 文件保存路径 + */ + public FileLogManagerServiceImpl(String fileSavePath) { + this.fileSavePath = fileSavePath; + } + + @Override + public List queryLogList(LogManagerParam logManagerParam) { + logManagerParam.setPageSize(1000); + PageResult pageResult = queryLogListPage(logManagerParam); + return pageResult.getRows(); + } + + @Override + public PageResult queryLogListPage(LogManagerParam logManagerParam) { + + // 文件日志,必须有AppName,否则文件太多太大 + if (ObjectUtil.isEmpty(logManagerParam.getAppName())) { + throw new LogException(APP_NAME_NOT_EXIST); + } + + // 文件日志,必须有开始时间,否则文件太多太大 + if (ObjectUtil.isEmpty(logManagerParam.getBeginDateTime())) { + throw new LogException(BEGIN_DATETIME_NOT_EXIST); + } + + // 获取文件路径 + String filePath = getLogPath(logManagerParam.getAppName(), logManagerParam.getBeginDateTime()); + + // 文件当前指针 + long filePointer = 0L; + + // 如果页数不等于1,则根据当前登陆用户信息取出上次读取文件的位置 + if (!logManagerParam.getPageNo().equals(1)) { + Object pointer = LoginContext.me().getLoginUser().getOtherInfos().get("filePointer"); + if (ObjectUtil.isNotEmpty(pointer)) { + filePointer = (long) pointer; + } + } + // 返回分页结果 + PageResult pageResult = new PageResult<>(); + pageResult.setPageSize(logManagerParam.getPageSize()); + + // 读取日志 + List dtos = readLog(filePath, filePointer, logManagerParam.getPageSize()); + pageResult.setRows(dtos); + + return pageResult; + } + + @Override + public void deleteLogs(LogManagerParam logManagerParam) { + + // 删除操作,必须有appName + if (ObjectUtil.isEmpty(logManagerParam.getAppName())) { + throw new LogException(APP_NAME_NOT_EXIST); + } + + // 删除操作,必须有appName + if (ObjectUtil.isEmpty(logManagerParam.getBeginDateTime())) { + throw new LogException(BEGIN_DATETIME_NOT_EXIST); + } + + // 文件日志,必须有结束时间,否则文件太多太大 + if (ObjectUtil.isEmpty(logManagerParam.getEndDateTime())) { + throw new LogException(END_DATETIME_NOT_EXIST); + } + + // 计算开始和结束两个时间之间的所有日期 + List dates = getIntervalDate(logManagerParam.getBeginDateTime(), logManagerParam.getEndDateTime()); + + // 查找每一天的日志 + for (String date : dates) { + + // 拼接文件名称 + String logPath = getLogPath(logManagerParam.getAppName(), date); + + // 删除日志 + if (FileUtil.exist(logPath)) { + FileUtil.del(logPath); + } + } + } + + /** + * 根据app名称和日期获取日志文件全路径 + * + * @param appName APP名称 + * @param date 那一天的日志 + * @return 文件全路径名称 + * @author majianguo + * @date 2020/11/3 下午4:29 + */ + private String getLogPath(String appName, String date) { + + // 根据传入的AppName和时间来定位确定一个唯一的文件名称 + String fileName = appName + FILE_CONTRACT_SYMBOL + DateUtil.parse(date).toDateStr() + FILE_SUFFIX; + + // 文件绝对路径生成,带文件名的完整路径 + String fileAbsolutePath = fileSavePath + File.separator; + return fileAbsolutePath + fileName; + } + + /** + * 从指定的文件指针处,开始读取指定行数的数据 + * + * @param path 文件全路径 + * @param filePointer 开始读取文件指针位置 + * @param lineNum 读取行数 + * @author majianguo + * @date 2020/11/3 下午1:36 + */ + private List readLog(String path, long filePointer, int lineNum) { + // 判断文件是否存在,不存在直接返回Null + if (!FileUtil.exist(path)) { + return null; + } + + // 返回结果集 + List readLines = new ArrayList<>(); + + RandomAccessFile file; + try { + // 创建随机读文件流 + file = new RandomAccessFile(path, "r"); + + // 设置文件指针 + file.seek(filePointer); + + // 从设置的指针处开始读取文件 + for (int i = 0; i < lineNum; ++i) { + String str = file.readLine(); + + // 读取到的字符串不为空 + if (ObjectUtil.isNotEmpty(str)) { + LogRecordDTO recordDTO = parseObject(str); + + // 格式转换没有问题就加入到列表中 + if (ObjectUtil.isNotEmpty(recordDTO)) { + readLines.add(recordDTO); + } + } + } + + // 在用户信息中记录当前用户读取的文件指针 + LoginContext.me().getLoginUser().getOtherInfos().put("filePointer", file.getFilePointer()); + + } catch (Exception e) { + if (log.isDebugEnabled()) { + e.printStackTrace(); + } + log.error(e.getMessage()); + } + return readLines; + } + + /** + * 根据传入的JSON字符串转换成JAVA对象 + * + * @param jsonStr json字符串 + * @return 返回格式化后的Java对象 如果格式错误,则返回Null + * @author majianguo + * @date 2020/11/3 下午2:16 + */ + private LogRecordDTO parseObject(String jsonStr) { + LogRecordDTO logRecordDTO = null; + try { + + // 这里接受到的jsonStr是乱码的,需要通过getBytes方法转换成字节数组,然后通过newString的方式再转换回来 + logRecordDTO = JSON.parseObject(new String(getBytes(jsonStr.toCharArray())), LogRecordDTO.class); + } catch (Exception e) { + if (log.isDebugEnabled()) { + e.printStackTrace(); + } + log.error(e.getMessage()); + } + return logRecordDTO; + } + + /** + * 解决readLine读取中文乱码问题 + * + * @param chars 乱码的字符串转数组 + * @author majianguo + * @date 2020/11/3 下午4:03 + */ + public static byte[] getBytes(char[] chars) { + byte[] result = new byte[chars.length]; + for (int i = 0; i < chars.length; i++) { + result[i] = (byte) chars[i]; + } + return result; + } + + /** + * 计算两个日期之间所有的日期 + * + * @param start 开始日期 间隔 + * @param end 结束日期 + * @author majianguo + * @date 2020/11/3 下午4:23 + */ + public static List getIntervalDate(String start, String end) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + + // 保存日期集合 + List list = new ArrayList<>(); + try { + + Date dateStart = sdf.parse(start); + Date dateEnd = sdf.parse(end); + Date date = dateStart; + + // 用Calendar 进行日期比较判断 + Calendar cd = Calendar.getInstance(); + while (date.getTime() <= dateEnd.getTime()) { + list.add(sdf.format(date)); + cd.setTime(date); + + // 增加一天 放入集合 + cd.add(Calendar.DATE, 1); + date = cd.getTime(); + } + + } catch (ParseException e) { + if (log.isDebugEnabled()) { + e.printStackTrace(); + } + log.error(e.getMessage()); + } + return list; + } + +} diff --git a/kernel-d-log/log-sdk-file/src/main/java/cn/stylefeng/roses/kernel/log/file/FileLogRecordServiceImpl.java b/kernel-d-log/log-sdk-file/src/main/java/cn/stylefeng/roses/kernel/log/file/FileLogRecordServiceImpl.java new file mode 100644 index 000000000..9c16c1ed7 --- /dev/null +++ b/kernel-d-log/log-sdk-file/src/main/java/cn/stylefeng/roses/kernel/log/file/FileLogRecordServiceImpl.java @@ -0,0 +1,329 @@ +package cn.stylefeng.roses.kernel.log.file; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.log.Log; +import cn.hutool.log.LogFactory; +import com.alibaba.fastjson.JSON; +import cn.stylefeng.roses.kernel.log.api.LogRecordApi; +import cn.stylefeng.roses.kernel.log.api.constants.LogFileConstants; +import cn.stylefeng.roses.kernel.log.api.pojo.record.LogRecordDTO; +import cn.stylefeng.roses.kernel.log.api.threadpool.LogManagerThreadPool; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import static cn.stylefeng.roses.kernel.log.api.constants.LogFileConstants.FILE_CONTRACT_SYMBOL; +import static cn.stylefeng.roses.kernel.log.api.constants.LogFileConstants.FILE_SUFFIX; + +/** + * 文件存储方式的日志记录器 + * + * @author fengshuonan + * @date 2020/10/28 14:52 + */ +@Service +public class FileLogRecordServiceImpl implements LogRecordApi { + + private final LogManagerThreadPool logManagerThreadPool; + + private final LogRefreshManager logRefreshManager; + + private final String fileSavePath; + + public FileLogRecordServiceImpl(String fileSavePath, LogManagerThreadPool logManagerThreadPool) { + this.fileSavePath = fileSavePath; + this.logManagerThreadPool = logManagerThreadPool; + this.logRefreshManager = new LogRefreshManager(); + this.logRefreshManager.start(); + } + + public FileLogRecordServiceImpl(String fileSavePath, LogManagerThreadPool logManagerThreadPool, long sleepTime, int maxCount) { + this.fileSavePath = fileSavePath; + this.logManagerThreadPool = logManagerThreadPool; + this.logRefreshManager = new LogRefreshManager(sleepTime, maxCount); + this.logRefreshManager.start(); + } + + /** + * 日志文件的组成形式应为appName-年-月-日.log + * + * @author fengshuonan + * @date 2020/10/28 15:53 + */ + @Override + public void recordLog(LogRecordDTO logRecordDTO) { + + if (logRecordDTO == null) { + return; + } + + // 输出日志 + recordLogByList(CollectionUtil.list(false, logRecordDTO)); + } + + /** + * 批量输出日志 + * + * @param list 待输出日志列表 + * @author majianguo + * @date 2020/11/2 下午2:59 + */ + @Override + public void recordLogByList(List list) { + + if (ObjectUtil.isEmpty(list)) { + return; + } + + // 获取appName + String appName = list.get(0).getAppName(); + if (StrUtil.isBlank(appName)) { + appName = LogFileConstants.DEFAULT_LOG_FILE_NAME; + } + + // 获取日志记录的日期 + Date dateTime = list.get(0).getDateTime(); + if (dateTime == null) { + dateTime = new Date(); + } + String dateStr = DateUtil.formatDate(dateTime); + + // 拼接文件名 + String fileName = appName + FILE_CONTRACT_SYMBOL + dateStr + FILE_SUFFIX; + + // 文件绝对路径生成,带文件名的完整路径 + String fileAbsolutePath = fileSavePath + File.separator + fileName; + + // 判断文件是否存在,不存在则创建 + boolean existFlag = FileUtil.exist(fileAbsolutePath); + if (!existFlag) { + FileUtil.touch(fileAbsolutePath); + } + + // 将对象转换成JSON输出 + List outList = new ArrayList<>(); + for (LogRecordDTO recordDTO : list) { + outList.add(JSON.toJSONString(recordDTO)); + } + + // 追加日志内容 + FileUtil.appendLines(outList, fileAbsolutePath, StandardCharsets.UTF_8); + } + + @Override + public void recordLogAsync(LogRecordDTO logRecordDTO) { + logManagerThreadPool.executeLog(new TimerTask() { + @Override + public void run() { + logRefreshManager.putLog(logRecordDTO); + } + }); + } + + /** + * 日志刷新管理器 + *

+ * 该类暂存所有将要写出到磁盘的日志,使用内存缓冲区减少对磁盘IO的操作 + *

+ * 该类维护一个最大日志数和一个刷新日志间隔,满足任意一个条件即可触发从内存写出日志到磁盘的操作 + * + * @author majianguo + * @date 2020/10/31 15:05 + */ + class LogRefreshManager extends Thread { + + /** + * Hutool日志对象 + */ + private final Log log = LogFactory.get(); + + /** + * 刷新日志间隔(默认3秒),单位毫秒 + */ + private final long sleepTime; + + /** + * 满足多少条就强制刷新一次(默认300条),该值只是一个大约值,实际记录并不会一定等于该值(可能会大于该值,不可能小于该值) + */ + private final int maxCount; + + /** + * 刷新数据时间标志,每次刷新都记录当前的时间戳,方便定时刷新准确判断上次刷新和本次刷新的时间间隔 + */ + private final AtomicLong refreshMark = new AtomicLong(); + + public LogRefreshManager() { + this.sleepTime = 3000; + this.maxCount = 300; + } + + public LogRefreshManager(long sleepTime) { + this.sleepTime = sleepTime; + this.maxCount = 300; + } + + public LogRefreshManager(int maxCount) { + this.sleepTime = 3000; + this.maxCount = maxCount; + } + + public LogRefreshManager(long sleepTime, int maxCount) { + this.sleepTime = sleepTime; + this.maxCount = maxCount; + } + + /** + * 未处理日志的消息队列 + */ + private final Queue queue = new ConcurrentLinkedQueue<>(); + + /** + * 消息总数,队列的size方法会遍历一遍队列,所以自己维护大小 + */ + public AtomicInteger count = new AtomicInteger(0); + + /** + * 往队列内新增一条日志数据 + * + * @param logRecordDTO 日志对象 + * @author majianguo + * @date 2020/10/31 14:59 + */ + public void putLog(LogRecordDTO logRecordDTO) { + + int queueDataCount = count.get(); + + // 如果是第一条消息,刷新一次refreshMark + if (queueDataCount == 0) { + refreshMark.getAndSet(System.currentTimeMillis()); + } + + // 如果后续写入磁盘的操作有异常(磁盘满了),为了防止日志刷不出去导致OOM,内存中最大只保留maxCount数一倍的日志,后续日志将丢弃 + if (queueDataCount >= (maxCount * 2)) { + return; + } + + queue.offer(logRecordDTO); + count.incrementAndGet(); + } + + /** + * 刷新日志到磁盘的操作 + * + * @author majianguo + * @date 2020/10/31 15:48 + */ + private void refresh() { + // 让睡眠线程本次不要再调本方法,睡眠至下次看refreshMark的值再决定要不要调用本方法 + refreshMark.getAndSet(System.currentTimeMillis()); + + // 获取总数 + int num = count.getAndSet(0); + + // 缓冲队列中所有的数据 + List cacheAll = new ArrayList<>(num); + + try { + // 在队列中读取num条数据,放入list + for (int i = 0; i < num; i++) { + LogRecordDTO item = queue.poll(); + if (null == item) { + break; + } + cacheAll.add(item); + } + + // 调用方法刷新到磁盘 + recordLogByList(cacheAll); + + } catch (Exception e) { + + // 有异常把日志刷回队列,不要丢掉(这里可能会导致日志顺序错乱) + for (LogRecordDTO recordDTO : cacheAll) { + queue.offer(recordDTO); + } + + // 打印日志 + if (log.isDebugEnabled()) { + e.printStackTrace(); + } + log.error(e.getMessage()); + } + + } + + /** + * 日志数据定时执行器 + *

+ * 用于定时检测日志数据是否可以写入数据 + * + * @author majianguo + * @date 2020/10/31 15:57 + */ + private void timing() { + try { + // 如果是激活状态,且消息数大于零,且符合上次调用refresh方法到目前时间的间隔,那就调用一次refresh方法 + if ((refreshMark.get() + sleepTime) <= System.currentTimeMillis() && count.get() > 0) { + refresh(); + } + } catch (Exception e) { + if (log.isDebugEnabled()) { + e.printStackTrace(); + } + log.error(e.getMessage()); + } + } + + /** + * 日志数据监听器 + *

+ * 用于监听日志消息队列,达到设定的数就开始执行刷入硬盘的操作 + * + * @author majianguo + * @date 2020/11/2 9:32 + */ + private void listener() { + try { + // 判断如果队列里面的数据大于等于设定的最大消息数,就调用refresh方法刷新一次数据 + if (count.get() >= maxCount) { + refresh(); + } + } catch (Exception e) { + if (log.isDebugEnabled()) { + e.printStackTrace(); + } + log.error(e.getMessage()); + } + } + + @Override + @SuppressWarnings("InfiniteLoopStatement") + public void run() { + try { + for (; ; ) { + // 消息监听器 + listener(); + // 定时任务监听器 + timing(); + TimeUnit.MILLISECONDS.sleep(10); + } + } catch (InterruptedException e) { + if (log.isDebugEnabled()) { + e.printStackTrace(); + } + log.error(e.getMessage()); + } + } + } + +} diff --git a/kernel-d-log/log-spring-boot-starter/README.md b/kernel-d-log/log-spring-boot-starter/README.md new file mode 100644 index 000000000..28da458de --- /dev/null +++ b/kernel-d-log/log-spring-boot-starter/README.md @@ -0,0 +1 @@ +日志的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-log/log-spring-boot-starter/pom.xml b/kernel-d-log/log-spring-boot-starter/pom.xml new file mode 100644 index 000000000..c8094fb70 --- /dev/null +++ b/kernel-d-log/log-spring-boot-starter/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-log + 1.0.0 + ../pom.xml + + + log-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + log-business-manage + 1.0.0 + + + + + cn.stylefeng.roses + log-business-requestapi + 1.0.0 + + + + + cn.stylefeng.roses + log-sdk-db + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-log/log-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/log/starter/GunsLogAutoConfiguration.java b/kernel-d-log/log-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/log/starter/GunsLogAutoConfiguration.java new file mode 100644 index 000000000..57a2468c1 --- /dev/null +++ b/kernel-d-log/log-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/log/starter/GunsLogAutoConfiguration.java @@ -0,0 +1,14 @@ +package cn.stylefeng.roses.kernel.log.starter; + +import org.springframework.context.annotation.Configuration; + +/** + * 日志的自动配置 + * + * @author fengshuonan + * @date 2020/12/1 17:12 + */ +@Configuration +public class GunsLogAutoConfiguration { + +} diff --git a/kernel-d-log/log-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-log/log-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..d9b7e33eb --- /dev/null +++ b/kernel-d-log/log-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.log.starter.GunsLogAutoConfiguration \ No newline at end of file diff --git a/kernel-d-log/pom.xml b/kernel-d-log/pom.xml new file mode 100644 index 000000000..b16355088 --- /dev/null +++ b/kernel-d-log/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-log + + pom + + + log-api + log-business-manage + log-business-requestapi + log-sdk-db + log-sdk-file + log-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-office/README.md b/kernel-d-office/README.md new file mode 100644 index 000000000..d811a52df --- /dev/null +++ b/kernel-d-office/README.md @@ -0,0 +1 @@ +office模块 文档操作封装 diff --git a/kernel-d-office/office-api/README.md b/kernel-d-office/office-api/README.md new file mode 100644 index 000000000..63663e5ea --- /dev/null +++ b/kernel-d-office/office-api/README.md @@ -0,0 +1 @@ +office模块的api \ No newline at end of file diff --git a/kernel-d-office/office-api/pom.xml b/kernel-d-office/office-api/pom.xml new file mode 100644 index 000000000..2162c7ae5 --- /dev/null +++ b/kernel-d-office/office-api/pom.xml @@ -0,0 +1,52 @@ + + + + cn.stylefeng.roses + kernel-d-office + 1.0.0 + ../pom.xml + + + 4.0.0 + + office-api + + jar + + + + + + + javax.servlet + javax.servlet-api + true + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + + \ No newline at end of file diff --git a/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/OfficeExcelApi.java b/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/OfficeExcelApi.java new file mode 100644 index 000000000..63ae59c3f --- /dev/null +++ b/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/OfficeExcelApi.java @@ -0,0 +1,46 @@ +package cn.stylefeng.roses.kernel.office.api; + +import cn.stylefeng.roses.kernel.office.api.pojo.report.ExcelExportParam; + +import java.io.InputStream; +import java.util.List; + +/** + * Excel 常用操作接口 + * + * @author luojie + * @date 2020/11/3 16:42 + */ +public interface OfficeExcelApi { + + /** + * 简单的导出Excel下载 + * + * @param excelExportParam Excel导出参数 + * @author luojie + * @date 2020/11/4 10:11 + */ + void easyExportDownload(ExcelExportParam excelExportParam); + + /** + * 简单的写入Excel文件到指定路径 + * + * @param excelExportParam Excel导出参数 + * @author luojie + * @date 2020/11/4 11:31 + */ + void easyWriteToFile(ExcelExportParam excelExportParam); + + /** + * 简单的读取Excel文件并返回实体类List集合 + * + * @param inputStream 流输入Excel文件的流对象 + * @param clazz 每行数据转换成的对象类 + * @return 对象类List集合 + * + * @author luojie + * @date 2020/11/4 13:54 + */ + List easyReadToList(InputStream inputStream, Class clazz); + +} diff --git a/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/constants/OfficeConstants.java b/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/constants/OfficeConstants.java new file mode 100644 index 000000000..1d50d41a6 --- /dev/null +++ b/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/constants/OfficeConstants.java @@ -0,0 +1,31 @@ +package cn.stylefeng.roses.kernel.office.api.constants; + +/** + * Office 模块常量 + * + * @author luojie + * @date 2020/11/4 10:17 + */ +public interface OfficeConstants { + + /** + * office模块的名称 + */ + String OFFICE_MODULE_NAME = "kernel-d-office"; + + /** + * 异常枚举的步进值 + */ + String OFFICE_EXCEPTION_STEP_CODE = "19"; + + /** + * Excel导出时默认Sheet的名称 + */ + String OFFICE_EXCEL_DEFAULT_SHEET_NAME = "Sheet"; + + /** + * Excel导出时默认文件名称 + */ + String OFFICE_EXCEL_EXPORT_DEFAULT_FILE_NAME = "export"; + +} diff --git a/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/exception/OfficeException.java b/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/exception/OfficeException.java new file mode 100644 index 000000000..82bf4ec38 --- /dev/null +++ b/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/exception/OfficeException.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.office.api.exception; + +import cn.stylefeng.roses.kernel.office.api.constants.OfficeConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; + +/** + * Office模块异常 + * + * @author luojie + * @date 2020/11/4 10:15 + */ +public class OfficeException extends ServiceException { + + public OfficeException(AbstractExceptionEnum exception) { + super(OfficeConstants.OFFICE_MODULE_NAME, exception); + } + + public OfficeException(String errorCode, String userTip) { + super(OfficeConstants.OFFICE_MODULE_NAME, errorCode, userTip); + } + +} diff --git a/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/exception/enums/OfficeExceptionEnum.java b/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/exception/enums/OfficeExceptionEnum.java new file mode 100644 index 000000000..d082ee600 --- /dev/null +++ b/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/exception/enums/OfficeExceptionEnum.java @@ -0,0 +1,47 @@ +package cn.stylefeng.roses.kernel.office.api.exception.enums; + +import cn.stylefeng.roses.kernel.office.api.constants.OfficeConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import lombok.Getter; + +/** + * Office模块相关异常枚举 + * + * @author luojie + * @date 2020/11/4 10:19 + */ +@Getter +public enum OfficeExceptionEnum implements AbstractExceptionEnum { + + /** + * Office操作异常 + */ + OFFICE_ERROR(RuleConstants.THIRD_ERROR_TYPE_CODE + OfficeConstants.OFFICE_EXCEPTION_STEP_CODE + "01", "Office操作异常,具体信息为:{}"), + + /** + * Excel导出 响应为空 + */ + OFFICE_EXCEL_EXPORT_RESPONSE_ISNULL(RuleConstants.THIRD_ERROR_TYPE_CODE + OfficeConstants.OFFICE_EXCEPTION_STEP_CODE + "02", "Excel导出响应为空"), + + /** + * Excel导出 实体类为空 + */ + OFFICE_EXCEL_EXPORT_ENTITY_CLASS_ISNULL(RuleConstants.THIRD_ERROR_TYPE_CODE + OfficeConstants.OFFICE_EXCEPTION_STEP_CODE + "03", "实体类为空"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + OfficeExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/pojo/report/ExcelExportParam.java b/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/pojo/report/ExcelExportParam.java new file mode 100644 index 000000000..1c80fca1d --- /dev/null +++ b/kernel-d-office/office-api/src/main/java/cn/stylefeng/roses/kernel/office/api/pojo/report/ExcelExportParam.java @@ -0,0 +1,53 @@ +package cn.stylefeng.roses.kernel.office.api.pojo.report; + +import com.alibaba.excel.support.ExcelTypeEnum; +import lombok.Data; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * Excel 导出参数 + * + * @author luojie + * @date 2020/11/4 10:02 + */ +@Data +public class ExcelExportParam { + + /** + * 需要导出的数据列表 + */ + List dataList; + + /** + * Excel每行数据转换成的对象类 + */ + Class clazz; + + /** + * 工作簿名称 导出/写入文件用 + */ + String sheetName; + + /** + * 下载提示的文件名 无需带上xls、xlsx后缀 + */ + String fileName; + + /** + * Excel类型 xls、xlsx + */ + ExcelTypeEnum excelTypeEnum; + + /** + * http 响应 + */ + HttpServletResponse response; + + /** + * 文件写入绝对路径 写入到服务器磁盘用 + */ + String excelFileWriteAbsolutePath; + +} diff --git a/kernel-d-office/office-sdk-excel/README.md b/kernel-d-office/office-sdk-excel/README.md new file mode 100644 index 000000000..9ee0b6fc4 --- /dev/null +++ b/kernel-d-office/office-sdk-excel/README.md @@ -0,0 +1 @@ +office模块的Excel操作封装 \ No newline at end of file diff --git a/kernel-d-office/office-sdk-excel/pom.xml b/kernel-d-office/office-sdk-excel/pom.xml new file mode 100644 index 000000000..4cd48b2c3 --- /dev/null +++ b/kernel-d-office/office-sdk-excel/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-office + 1.0.0 + ../pom.xml + + + office-sdk-excel + + jar + + + + + + cn.stylefeng.roses + office-api + 1.0.0 + + + + + + javax.servlet + javax.servlet-api + true + + + + + + + \ No newline at end of file diff --git a/kernel-d-office/office-sdk-excel/src/main/java/cn/stylefeng/roses/kernel/office/excel/OfficeExcel.java b/kernel-d-office/office-sdk-excel/src/main/java/cn/stylefeng/roses/kernel/office/excel/OfficeExcel.java new file mode 100644 index 000000000..3d5215afa --- /dev/null +++ b/kernel-d-office/office-sdk-excel/src/main/java/cn/stylefeng/roses/kernel/office/excel/OfficeExcel.java @@ -0,0 +1,138 @@ +package cn.stylefeng.roses.kernel.office.excel; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.office.excel.listener.SimpleDataListener; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.support.ExcelTypeEnum; +import cn.stylefeng.roses.kernel.office.api.OfficeExcelApi; +import cn.stylefeng.roses.kernel.office.api.constants.OfficeConstants; +import cn.stylefeng.roses.kernel.office.api.exception.OfficeException; +import cn.stylefeng.roses.kernel.office.api.exception.enums.OfficeExceptionEnum; +import cn.stylefeng.roses.kernel.office.api.pojo.report.ExcelExportParam; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletResponse; +import java.io.InputStream; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +/** + * Excel 常用操作接口实现 + * + * @author luojie + * @date 2020/11/3 16:45 + */ +@Slf4j +@Service +public class OfficeExcel implements OfficeExcelApi { + + @Override + public List easyReadToList(InputStream inputStream, Class clazz) { + if (inputStream == null) { + return new ArrayList(); + } + + // 创建一个简单的数据监听器 + SimpleDataListener readListener = new SimpleDataListener(); + + // 读取文件 + try { + EasyExcel.read(inputStream, clazz, readListener).sheet().doRead(); + } catch (Exception e) { + log.error(e.getMessage()); + + // 组装提示信息 + String userTip = OfficeExceptionEnum.OFFICE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new OfficeException(OfficeExceptionEnum.OFFICE_ERROR.getErrorCode(), finalUserTip); + } + + return readListener.getDataList(); + } + + @Override + public void easyWriteToFile(ExcelExportParam excelExportParam) { + + // 默认值 + createDefaultValue(excelExportParam); + + ExcelTypeEnum excelTypeEnum = excelExportParam.getExcelTypeEnum(); + String excelFileWriteAbsolutePath = excelExportParam.getExcelFileWriteAbsolutePath(); + + try { + EasyExcel.write(excelFileWriteAbsolutePath, excelExportParam.getClazz()).excelType(excelTypeEnum).sheet(excelExportParam.getSheetName()).doWrite(excelExportParam.getDataList()); + } catch (Exception e) { + log.error(e.getMessage()); + + // 组装提示信息 + String userTip = OfficeExceptionEnum.OFFICE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new OfficeException(OfficeExceptionEnum.OFFICE_ERROR.getErrorCode(), finalUserTip); + } + } + + @Override + public void easyExportDownload(ExcelExportParam excelExportParam) { + if (ObjectUtil.isEmpty(excelExportParam)) { + return; + } + + try { + HttpServletResponse response = excelExportParam.getResponse(); + if (response == null) { + throw new OfficeException(OfficeExceptionEnum.OFFICE_EXCEL_EXPORT_RESPONSE_ISNULL); + } + + if (excelExportParam.getClazz() == null) { + throw new OfficeException(OfficeExceptionEnum.OFFICE_EXCEL_EXPORT_ENTITY_CLASS_ISNULL); + } + + // 默认值 + createDefaultValue(excelExportParam); + + ExcelTypeEnum excelTypeEnum = excelExportParam.getExcelTypeEnum(); + + response.setContentType("application/vnd.ms-excel"); + response.setCharacterEncoding("utf-8"); + String fileName = URLEncoder.encode(excelExportParam.getFileName(), "UTF-8").replaceAll("\\+", "%20"); + response.setHeader("Content-disposition", String.format("%s%s", "attachment;filename*=utf-8''", fileName, excelTypeEnum.getValue())); + + EasyExcel.write(response.getOutputStream(), excelExportParam.getClazz()).excelType(excelTypeEnum).sheet(excelExportParam.getSheetName()).doWrite(excelExportParam.getDataList()); + + } catch (Exception e) { + log.error(e.getMessage()); + + // 组装提示信息 + String userTip = OfficeExceptionEnum.OFFICE_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, e.getMessage()); + throw new OfficeException(OfficeExceptionEnum.OFFICE_ERROR.getErrorCode(), finalUserTip); + } + + } + + /** + * excel导出文件的默认属性 + * + * @param param Excel导出参数 + * @author luojie + * @date 2020/11/4 11:45 + */ + private void createDefaultValue(ExcelExportParam param) { + + if (StrUtil.isEmpty(param.getSheetName())) { + param.setSheetName(OfficeConstants.OFFICE_EXCEL_DEFAULT_SHEET_NAME); + } + + if (StrUtil.isEmpty(param.getFileName())) { + param.setFileName(OfficeConstants.OFFICE_EXCEL_EXPORT_DEFAULT_FILE_NAME); + } + + if (param.getExcelTypeEnum() == null) { + param.setExcelTypeEnum(ExcelTypeEnum.XLSX); + } + } + +} diff --git a/kernel-d-office/office-sdk-excel/src/main/java/cn/stylefeng/roses/kernel/office/excel/listener/SimpleDataListener.java b/kernel-d-office/office-sdk-excel/src/main/java/cn/stylefeng/roses/kernel/office/excel/listener/SimpleDataListener.java new file mode 100644 index 000000000..c3ecf8ed9 --- /dev/null +++ b/kernel-d-office/office-sdk-excel/src/main/java/cn/stylefeng/roses/kernel/office/excel/listener/SimpleDataListener.java @@ -0,0 +1,58 @@ +package cn.stylefeng.roses.kernel.office.excel.listener; + +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; + +import java.util.ArrayList; +import java.util.List; + +/** + * 简单的数据监听器 + * + * @author luojie + * @date 2020/11/4 13:55 + */ +public class SimpleDataListener extends AnalysisEventListener { + + /** + * 实体类List集合 + */ + private final List dataList = new ArrayList<>(); + + /** + * 获取实体类List集合 + * + * @return 实体类List集合 + * @author luojie + * @date 2020/11/4 16:49 + */ + public List getDataList() { + return dataList; + } + + /** + * 这个每一条数据解析都会来调用 + * + * @param data Excel每行数据转换成的对象类 + * @param context EasyExcel分析上下文 + * @author luojie + * @date 2020/11/4 16:49 + */ + @Override + public void invoke(T data, AnalysisContext context) { + // 添加到集合中 + dataList.add(data); + } + + /** + * 所有数据解析完成了 都会来调用 + * + * @param context EasyExcel分析上下文 + * @author luojie + * @date 2020/11/4 16:49 + */ + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + } +} + diff --git a/kernel-d-office/office-spring-boot-starter/README.md b/kernel-d-office/office-spring-boot-starter/README.md new file mode 100644 index 000000000..3c58f1413 --- /dev/null +++ b/kernel-d-office/office-spring-boot-starter/README.md @@ -0,0 +1 @@ +office的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-office/office-spring-boot-starter/pom.xml b/kernel-d-office/office-spring-boot-starter/pom.xml new file mode 100644 index 000000000..f748dc4cc --- /dev/null +++ b/kernel-d-office/office-spring-boot-starter/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-office + 1.0.0 + ../pom.xml + + + office-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + office-sdk-excel + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-office/pom.xml b/kernel-d-office/pom.xml new file mode 100644 index 000000000..e7fd4f283 --- /dev/null +++ b/kernel-d-office/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-office + + pom + + + office-api + office-sdk-excel + office-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + com.alibaba + easyexcel + 2.2.6 + + + + + diff --git a/kernel-d-pinyin/README.md b/kernel-d-pinyin/README.md new file mode 100644 index 000000000..dfb47bbf7 --- /dev/null +++ b/kernel-d-pinyin/README.md @@ -0,0 +1 @@ +拼音工具模块 \ No newline at end of file diff --git a/kernel-d-pinyin/pinyin-api/README.md b/kernel-d-pinyin/pinyin-api/README.md new file mode 100644 index 000000000..b189a7842 --- /dev/null +++ b/kernel-d-pinyin/pinyin-api/README.md @@ -0,0 +1 @@ +拼音模块的api \ No newline at end of file diff --git a/kernel-d-pinyin/pinyin-api/pom.xml b/kernel-d-pinyin/pinyin-api/pom.xml new file mode 100644 index 000000000..b7f02ebe9 --- /dev/null +++ b/kernel-d-pinyin/pinyin-api/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-pinyin + 1.0.0 + ../pom.xml + + + pinyin-api + + jar + + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/PinYinApi.java b/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/PinYinApi.java new file mode 100644 index 000000000..faae55c52 --- /dev/null +++ b/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/PinYinApi.java @@ -0,0 +1,72 @@ +package cn.stylefeng.roses.kernel.pinyin.api; + +/** + * 拼音转化接口 + * + * @author fengshuonan + * @date 2020/12/4 9:30 + */ +public interface PinYinApi { + + /** + * 获取姓氏的首字母大写 + *

+ * 百家姓涉及到多音字的,都配置在properties中,优先读取properties中的映射 + *

+ * 例如:张 -> Z + * 例如:单 -> S + * + * @param lastnameChines 中文姓氏 + * @return 姓氏的首字母大写 + * @author fengshuonan + * @date 2020/12/4 10:34 + */ + String getLastnameFirstLetterUpper(String lastnameChines); + + /** + * 将文字转为汉语拼音,取每个字的第一个字母大写 + *

+ * 例如:你好 => NH + * + * @param chineseString 中文字符串 + * @return 中文字符串每个字的第一个字母大写 + * @author fengshuonan + * @date 2020/12/4 13:41 + */ + String getChineseStringFirstLetterUpper(String chineseString); + + /** + * 获取汉字字符串的全拼拼音 + *

+ * 例如:中国人 -> zhongguoren + * + * @param chineseString 中文字符串 + * @return 拼音形式的字符串 + * @author fengshuonan + * @date 2020/12/4 14:55 + */ + String parsePinyinString(String chineseString); + + /** + * 将中文字符串转化为汉语拼音,取每个字的首字母 + *

+ * 例如:中国人 -> zgr + * + * @param chinesString 中文字符串 + * @return 每个字的拼音首字母组合 + * @author fengshuonan + * @date 2020/12/4 15:18 + */ + String parseEveryPinyinFirstLetter(String chinesString); + + /** + * 将中文字符串转移为ASCII码 + * + * @param chineseString 中文字符串 + * @return ASCII码 + * @author fengshuonan + * @date 2020/12/4 15:21 + */ + String getChineseAscii(String chineseString); + +} diff --git a/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/constants/PinyinConstants.java b/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/constants/PinyinConstants.java new file mode 100644 index 000000000..1b5bd18b2 --- /dev/null +++ b/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/constants/PinyinConstants.java @@ -0,0 +1,26 @@ +package cn.stylefeng.roses.kernel.pinyin.api.constants; + +/** + * 拼音模块常量 + * + * @author fengshuonan + * @date 2020/12/3 19:24 + */ +public interface PinyinConstants { + + /** + * 邮件模块的名称 + */ + String PINYIN_MODULE_NAME = "kernel-d-pinyin"; + + /** + * 异常枚举的步进值 + */ + String PINYIN_EXCEPTION_STEP_CODE = "22"; + + /** + * 中文字符的正则表达式 + */ + String CHINESE_WORDS_REGEX = "[\u4E00-\u9FA5]+"; + +} diff --git a/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/context/PinyinContext.java b/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/context/PinyinContext.java new file mode 100644 index 000000000..f47eeb0a5 --- /dev/null +++ b/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/context/PinyinContext.java @@ -0,0 +1,24 @@ +package cn.stylefeng.roses.kernel.pinyin.api.context; + +import cn.hutool.extra.spring.SpringUtil; +import cn.stylefeng.roses.kernel.pinyin.api.PinYinApi; + +/** + * 拼音工具类快速获取 + * + * @author fengshuonan + * @date 2020/12/4 9:31 + */ +public class PinyinContext { + + /** + * 获取拼音工具类 + * + * @author fengshuonan + * @date 2020/12/4 9:36 + */ + public static PinYinApi me() { + return SpringUtil.getBean(PinYinApi.class); + } + +} diff --git a/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/exception/PinyinException.java b/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/exception/PinyinException.java new file mode 100644 index 000000000..8714deb0c --- /dev/null +++ b/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/exception/PinyinException.java @@ -0,0 +1,25 @@ +package cn.stylefeng.roses.kernel.pinyin.api.exception; + +import cn.stylefeng.roses.kernel.pinyin.api.constants.PinyinConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; +import lombok.Getter; + +/** + * 拼音异常 + * + * @author fengshuonan + * @date 2020/12/3 18:10 + */ +@Getter +public class PinyinException extends ServiceException { + + public PinyinException(AbstractExceptionEnum exceptionEnum, String userTip) { + super(PinyinConstants.PINYIN_MODULE_NAME, exceptionEnum.getErrorCode(), userTip); + } + + public PinyinException(AbstractExceptionEnum exceptionEnum) { + super(PinyinConstants.PINYIN_MODULE_NAME, exceptionEnum); + } + +} diff --git a/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/exception/enums/PinyinExceptionEnum.java b/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/exception/enums/PinyinExceptionEnum.java new file mode 100644 index 000000000..85c13e6cc --- /dev/null +++ b/kernel-d-pinyin/pinyin-api/src/main/java/cn/stylefeng/roses/kernel/pinyin/api/exception/enums/PinyinExceptionEnum.java @@ -0,0 +1,37 @@ +package cn.stylefeng.roses.kernel.pinyin.api.exception.enums; + +import cn.stylefeng.roses.kernel.pinyin.api.constants.PinyinConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import lombok.Getter; + +/** + * 拼音工具相关异常 + * + * @author fengshuonan + * @date 2020/12/4 9:32 + */ +@Getter +public enum PinyinExceptionEnum implements AbstractExceptionEnum { + + /** + * 字符不能转成汉语拼音 + */ + PARSE_ERROR(RuleConstants.THIRD_ERROR_TYPE_CODE + PinyinConstants.PINYIN_EXCEPTION_STEP_CODE + "01", "拼音转化异常,具体信息:{}"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + PinyinExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-pinyin/pinyin-sdk-pinyin4j/README.md b/kernel-d-pinyin/pinyin-sdk-pinyin4j/README.md new file mode 100644 index 000000000..4ab8172a1 --- /dev/null +++ b/kernel-d-pinyin/pinyin-sdk-pinyin4j/README.md @@ -0,0 +1 @@ +拼音工具类sdk \ No newline at end of file diff --git a/kernel-d-pinyin/pinyin-sdk-pinyin4j/pom.xml b/kernel-d-pinyin/pinyin-sdk-pinyin4j/pom.xml new file mode 100644 index 000000000..bbcb9be5d --- /dev/null +++ b/kernel-d-pinyin/pinyin-sdk-pinyin4j/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-pinyin + 1.0.0 + ../pom.xml + + + pinyin-sdk-pinyin4j + + jar + + + + + + cn.stylefeng.roses + pinyin-api + 1.0.0 + + + + + com.belerweb + pinyin4j + 2.5.0 + + + + + diff --git a/kernel-d-pinyin/pinyin-sdk-pinyin4j/src/main/java/cn/stylefeng/roses/kernel/pinyin/PinyinServiceImpl.java b/kernel-d-pinyin/pinyin-sdk-pinyin4j/src/main/java/cn/stylefeng/roses/kernel/pinyin/PinyinServiceImpl.java new file mode 100644 index 000000000..f4bd94b1f --- /dev/null +++ b/kernel-d-pinyin/pinyin-sdk-pinyin4j/src/main/java/cn/stylefeng/roses/kernel/pinyin/PinyinServiceImpl.java @@ -0,0 +1,192 @@ +package cn.stylefeng.roses.kernel.pinyin; + +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.pinyin.api.PinYinApi; +import cn.stylefeng.roses.kernel.pinyin.api.exception.PinyinException; +import net.sourceforge.pinyin4j.PinyinHelper; +import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType; +import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat; +import net.sourceforge.pinyin4j.format.HanyuPinyinToneType; +import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType; +import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination; + +import java.util.Properties; +import java.util.Set; + +import static cn.stylefeng.roses.kernel.pinyin.api.constants.PinyinConstants.CHINESE_WORDS_REGEX; +import static cn.stylefeng.roses.kernel.pinyin.api.exception.enums.PinyinExceptionEnum.PARSE_ERROR; + +/** + * 拼音工具类的实现 + * + * @author fengshuonan + * @date 2020/12/4 9:40 + */ +public class PinyinServiceImpl implements PinYinApi { + + private final Properties properties = new Properties(); + + public PinyinServiceImpl() { + init(); + } + + /** + * 初始化多音字中文字和拼音首字母大写的映射 + * + * @author fengshuonan + * @date 2020/12/4 14:09 + */ + public void init() { + // 百家姓多音字的汉语-首字母大写的映射 + properties.put("\u533A", "O"); + properties.put("\u665F", "C"); + properties.put("\u4E50", "Y"); + properties.put("\u5458", "Y"); + properties.put("\u8D20", "Y"); + properties.put("\u9ED1", "H"); + properties.put("\u91CD", "C"); + properties.put("\u4EC7", "Q"); + properties.put("\u79D8", "B"); + properties.put("\u51BC", "X"); + properties.put("\u89E3", "X"); + properties.put("\u6298", "S"); + properties.put("\u5355", "S"); + properties.put("\u6734", "P"); + properties.put("\u7FDF", "Z"); + properties.put("\u67E5", "Z"); + properties.put("\u76D6", "G"); + properties.put("\u4E07\u4FDF", "M I"); + properties.put("\u5C09\u8FDF", "Y C"); + } + + @Override + public String getLastnameFirstLetterUpper(String lastnameChines) { + + // 从百家姓多音字映射中去比对,有没有包含多音字的,包含多音字姓的以映射表为准,不包含的以普通拼音解析为准 + Set keys = properties.keySet(); + for (Object lastNameObject : keys) { + String lastname = (String)lastNameObject; + if (lastnameChines.length() >= lastname.length() && lastnameChines.startsWith(lastname)) { + return properties.getProperty(lastname); + } + } + + // 返回普通姓氏的首字符大写 + return getFirstLetters(lastnameChines, HanyuPinyinCaseType.UPPERCASE); + } + + @Override + public String getChineseStringFirstLetterUpper(String chineseString) { + return getFirstLetters(chineseString, HanyuPinyinCaseType.UPPERCASE); + } + + @Override + public String parsePinyinString(String chineseString) { + + // 拆分每个汉字 + char[] chineseWordsArray = chineseString.toCharArray(); + + // 拼音格式化 + HanyuPinyinOutputFormat hanyuPinyinOutputFormat = new HanyuPinyinOutputFormat(); + hanyuPinyinOutputFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE); + hanyuPinyinOutputFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE); + hanyuPinyinOutputFormat.setVCharType(HanyuPinyinVCharType.WITH_V); + + // 最终的拼音字符串 + StringBuilder finalPinyinString = new StringBuilder(); + try { + // 每一个汉字分别转化为拼音 + for (char chineseWord : chineseWordsArray) { + // 判断是否为汉字字符 + if (Character.toString(chineseWord).matches(CHINESE_WORDS_REGEX)) { + String[] strings = PinyinHelper.toHanyuPinyinStringArray(chineseWord, hanyuPinyinOutputFormat); + finalPinyinString.append(strings[0]); + } else { + finalPinyinString.append(chineseWord); + } + } + return finalPinyinString.toString(); + } catch (BadHanyuPinyinOutputFormatCombination e1) { + String userTip = StrUtil.format(PARSE_ERROR.getUserTip(), e1.getMessage()); + throw new PinyinException(PARSE_ERROR, userTip); + } + } + + @Override + public String parseEveryPinyinFirstLetter(String chinesString) { + StringBuilder convert = new StringBuilder(); + for (int i = 0; i < chinesString.length(); i++) { + char word = chinesString.charAt(i); + String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(word); + if (pinyinArray != null) { + convert.append(pinyinArray[0].charAt(0)); + } else { + convert.append(word); + } + } + return convert.toString(); + } + + @Override + public String getChineseAscii(String chineseString) { + StringBuilder strBuf = new StringBuilder(); + byte[] bGBK = chineseString.getBytes(); + for (byte b : bGBK) { + strBuf.append(Integer.toHexString(b & 0xff)); + } + return strBuf.toString(); + } + + /** + * 获取中文字符串的首字母,大小写根据传参决定 + * + * @param chineseString 中文字符串 + * @param caseType 大小写类型 + * @return 首字母大小写 + * @author fengshuonan + * @date 2020/12/4 14:14 + */ + private static String getFirstLetters(String chineseString, HanyuPinyinCaseType caseType) { + + char[] chinesWords = chineseString.trim().toCharArray(); + StringBuilder hanyupinyin = new StringBuilder(); + + HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat(); + + // 输出拼音全部大写 + defaultFormat.setCaseType(caseType); + + // 不带声调 + defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE); + + try { + for (char word : chinesWords) { + String str = String.valueOf(word); + + // 如果字符是中文,则将中文转为汉语拼音,并取第一个字母 + if (str.matches(CHINESE_WORDS_REGEX)) { + hanyupinyin.append(PinyinHelper.toHanyuPinyinStringArray(word, defaultFormat)[0].charAt(0)); + } + // 如果字符是数字,取数字 + else if (str.matches("[0-9]+")) { + hanyupinyin.append(word); + } + // 如果字符是字母,取字母 + else if (str.matches("[a-zA-Z]+")) { + hanyupinyin.append(word); + + } + // 否则不转换,如果是标点符号的话,也带着 + else { + hanyupinyin.append(word); + } + } + } catch (BadHanyuPinyinOutputFormatCombination e) { + String userTip = StrUtil.format(PARSE_ERROR.getUserTip(), e.getMessage()); + throw new PinyinException(PARSE_ERROR, userTip); + } + + return hanyupinyin.toString(); + } + +} diff --git a/kernel-d-pinyin/pinyin-spring-boot-starter/README.md b/kernel-d-pinyin/pinyin-spring-boot-starter/README.md new file mode 100644 index 000000000..35520bacf --- /dev/null +++ b/kernel-d-pinyin/pinyin-spring-boot-starter/README.md @@ -0,0 +1 @@ +拼音模块的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-pinyin/pinyin-spring-boot-starter/pom.xml b/kernel-d-pinyin/pinyin-spring-boot-starter/pom.xml new file mode 100644 index 000000000..1a65c267f --- /dev/null +++ b/kernel-d-pinyin/pinyin-spring-boot-starter/pom.xml @@ -0,0 +1,79 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-pinyin + 1.0.0 + ../pom.xml + + + pinyin-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + pinyin-sdk-pinyin4j + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + compile + + jar + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + + package + + jar + + + + + none + + + -Xdoclint:none + + + + + + + diff --git a/kernel-d-pinyin/pinyin-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/pinyin/starter/GunsPinyinAutoConfiguration.java b/kernel-d-pinyin/pinyin-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/pinyin/starter/GunsPinyinAutoConfiguration.java new file mode 100644 index 000000000..a455bc68d --- /dev/null +++ b/kernel-d-pinyin/pinyin-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/pinyin/starter/GunsPinyinAutoConfiguration.java @@ -0,0 +1,30 @@ +package cn.stylefeng.roses.kernel.pinyin.starter; + +import cn.stylefeng.roses.kernel.pinyin.PinyinServiceImpl; +import cn.stylefeng.roses.kernel.pinyin.api.PinYinApi; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 拼音的自动配置 + * + * @author fengshuonan + * @date 2020/12/4 15:28 + */ +@Configuration +public class GunsPinyinAutoConfiguration { + + /** + * 拼音工具接口的封装 + * + * @author fengshuonan + * @date 2020/12/4 15:29 + */ + @Bean + @ConditionalOnMissingBean(PinYinApi.class) + public PinYinApi pinYinApi() { + return new PinyinServiceImpl(); + } + +} diff --git a/kernel-d-pinyin/pinyin-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-pinyin/pinyin-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..8172be13c --- /dev/null +++ b/kernel-d-pinyin/pinyin-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.pinyin.starter.GunsPinyinAutoConfiguration \ No newline at end of file diff --git a/kernel-d-pinyin/pom.xml b/kernel-d-pinyin/pom.xml new file mode 100644 index 000000000..f9c90c864 --- /dev/null +++ b/kernel-d-pinyin/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-pinyin + + pom + + + pinyin-api + pinyin-sdk-pinyin4j + pinyin-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-scanner/README.md b/kernel-d-scanner/README.md new file mode 100644 index 000000000..8a27250fe --- /dev/null +++ b/kernel-d-scanner/README.md @@ -0,0 +1,5 @@ +资源,指的是项目中的接口资源,一个资源对应一个Controller的接口 + +资源也是和前端的ui组件以及菜单,互相绑定的一个标识,绑定后,前端的ui组件展不展示,可以通过后端判断该用户有没有对应资源来进行智展示 + +本模块是搜集资源的扫描器,在spring启动的时候会搜集所有控制器上的@ApiResource注解 \ No newline at end of file diff --git a/kernel-d-scanner/pom.xml b/kernel-d-scanner/pom.xml new file mode 100644 index 000000000..86c2cb143 --- /dev/null +++ b/kernel-d-scanner/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-scanner + + pom + + + scanner-api + scanner-sdk-scanner + scanner-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-scanner/scanner-api/README.md b/kernel-d-scanner/scanner-api/README.md new file mode 100644 index 000000000..28b321a77 --- /dev/null +++ b/kernel-d-scanner/scanner-api/README.md @@ -0,0 +1 @@ +资源扫描模块的api \ No newline at end of file diff --git a/kernel-d-scanner/scanner-api/pom.xml b/kernel-d-scanner/scanner-api/pom.xml new file mode 100644 index 000000000..a6364a90a --- /dev/null +++ b/kernel-d-scanner/scanner-api/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-scanner + 1.0.0 + ../pom.xml + + + scanner-api + + jar + + + + + + + javax.servlet + javax.servlet-api + provided + + + org.springframework + spring-web + provided + + + + + diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/ResourceCollectorApi.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/ResourceCollectorApi.java new file mode 100644 index 000000000..a1b4ab8ad --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/ResourceCollectorApi.java @@ -0,0 +1,107 @@ +package cn.stylefeng.roses.kernel.resource.api; + +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceDefinition; + +import java.util.List; +import java.util.Map; + +/** + * 权限资源收集器,搜集本项目中的资源,仅搜集并缓存起来,不持久化 + * + * @author fengshuonan + * @date 2018-01-03-下午3:00 + */ +public interface ResourceCollectorApi { + + /** + * 保存所有扫描到的资源 + * + * @param apiResource 被存储的资源集合 + * @author fengshuonan + * @date 2020/10/19 15:26 + */ + void collectResources(List apiResource); + + /** + * 通过资源编码获取资源详情 + * + * @param resourceCode 资源编码,形如 this-system$user_manager$get_users + * @return 资源对象的详细信息 + * @author fengshuonan + * @date 2020/10/19 15:32 + */ + ResourceDefinition getResource(String resourceCode); + + /** + * 获取当前运行项目的所有资源 + * + * @return 资源集合 + * @author fengshuonan + * @date 2020/10/19 15:53 + */ + List getAllResources(); + + /** + * 通过模块编码获取资源 + * + * @param code 模块编码,一般为下划线分割的控制器前缀,不带Controller + * @return 资源集合 + * @author fengshuonan + * @date 2020/10/19 15:53 + */ + List getResourcesByModularCode(String code); + + /** + * 通过资源code获取资源中文名称 + * + * @param code 资源编码 + * @return 资源中文名称 + * @author fengshuonan + * @date 2020/10/19 15:56 + */ + String getResourceName(String code); + + /** + * 添加资源的code和名称 + * + * @param code 资源编码 + * @param name 资源名称 + * @author fengshuonan + * @date 2020/10/19 16:02 + */ + void bindResourceName(String code, String name); + + /** + * 获取所有模块资源 + *

+ * 第一个key是模块名称,是下划线分割的控制器名称,不带Controller结尾 + *

+ * 第二个key是资源的编码 + * + * @return 当前项目所有模块的资源集合 + * @author fengshuonan + * @date 2020/10/19 16:03 + */ + Map> getModularResources(); + + /** + * 通过编码获取url + * + * @param code 资源编码 + * @return 资源的url + * @author fengshuonan + * @date 2020/10/19 16:17 + */ + String getResourceUrl(String code); + + /** + * 通过url获取资源声明 + * + * @param url 资源的url + * @return 资源详情 + * @author fengshuonan + * @date 2020/10/19 16:17 + */ + ResourceDefinition getResourceByUrl(String url); + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/ResourceReportApi.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/ResourceReportApi.java new file mode 100644 index 000000000..f1ac180f6 --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/ResourceReportApi.java @@ -0,0 +1,30 @@ +package cn.stylefeng.roses.kernel.resource.api; + +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ReportResourceParam; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * 资源持久化服务api,将扫描的资源汇报给系统管理用 + * + * @author fengshuonan + * @date 2018-02-06 14:30 + */ +public interface ResourceReportApi { + + /** + * 持久化资源集合到某个服务中 + *

+ * 如果是单体项目,则吧资源汇报给本服务 + *

+ * 如果是微服务项目,则会有个consumer会将本服务的资源发送给资源管理者(一般为system服务) + * + * @param reportResourceReq 资源汇报接口 + * @author fengshuonan + * @date 2020/10/19 22:02 + */ + @RequestMapping(value = "/resourceService/reportResources", method = RequestMethod.POST) + void reportResources(@RequestBody ReportResourceParam reportResourceReq); + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/ApiResource.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/ApiResource.java new file mode 100644 index 000000000..8fb26d144 --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/ApiResource.java @@ -0,0 +1,76 @@ +package cn.stylefeng.roses.kernel.resource.api.annotation; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.lang.annotation.*; + +/** + * 资源标识,此注解代替Spring Mvc的@RequestMapping注解 + *

+ * 目的是为了在使用Spring Mvc的基础之上,增加对接口权限的控制功能 + * + * @author fengshuonan + * @date 2018-01-03-下午2:56 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.POST) +public @interface ApiResource { + + /** + *

+     * 资源编码唯一标识.
+     *
+     * 说明:
+     *     1.可不填写此注解属性.
+     *     2.若不填写,则默认生成的编码标识为: 控制器类名称 + 分隔符 + 方法名称.
+     *     3.若编码存在重复则系统启动异常
+     *
+     * 
+ */ + String code() default ""; + + /** + *

此属性用在一个程序承载两个业务系统的情况下,标识这个资源所属的模块,用在控制器上

+ */ + String appCode() default ""; + + /** + * 资源名称(必填项) + */ + String name() default ""; + + /** + * 是否是菜单(true-是菜单标识,false-不是菜单标识) + */ + boolean menuFlag() default false; + + /** + * 需要登录(true-需要登录,false-不需要登录) + */ + boolean requiredLogin() default true; + + /** + * 需要鉴权(true-需要鉴权,false-不需要鉴权) + */ + boolean requiredPermission() default true; + + /** + * 资源的响应类型,用于生成api文档 + */ + Class responseClass() default Void.class; + + /** + * 请求路径(同RequestMapping) + */ + @AliasFor(annotation = RequestMapping.class) String[] path() default {}; + + /** + * 请求的http方法(同RequestMapping) + */ + @AliasFor(annotation = RequestMapping.class) RequestMethod[] method() default {}; + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/GetResource.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/GetResource.java new file mode 100644 index 000000000..9e5703d5a --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/GetResource.java @@ -0,0 +1,72 @@ +package cn.stylefeng.roses.kernel.resource.api.annotation; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.lang.annotation.*; + +/** + * 资源标识,此注解代替Spring Mvc的@GetMapping注解 + *

+ * 目的是为了在使用Spring Mvc的基础之上,增加对接口权限的控制功能 + * + * @author fengshuonan + * @date 2018-01-03-下午2:56 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.POST) +public @interface GetResource { + + /** + *

+     * 资源编码唯一标识.
+     *
+     * 说明:
+     *     1.可不填写此注解属性.
+     *     2.若不填写,则默认生成的编码标识为: 控制器类名称 + 分隔符 + 方法名称.
+     *     3.若编码存在重复则系统启动异常
+     *
+     * 
+ */ + String code() default ""; + + /** + * 资源名称(必填项) + */ + String name() default ""; + + /** + * 是否是菜单(true-是菜单标识,false-不是菜单标识) + */ + boolean menuFlag() default false; + + /** + * 需要登录(true-需要登录,false-不需要登录) + */ + boolean requiredLogin() default true; + + /** + * 需要鉴权(true-需要鉴权,false-不需要鉴权) + */ + boolean requiredPermission() default true; + + /** + * 资源的响应类型,用于生成api文档 + */ + Class responseClass() default Void.class; + + /** + * 请求路径(同RequestMapping) + */ + @AliasFor(annotation = RequestMapping.class) + String[] path() default {}; + + /** + * 请求的http方法(同RequestMapping) + */ + @AliasFor(annotation = RequestMapping.class) + RequestMethod[] method() default RequestMethod.GET; +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/PostResource.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/PostResource.java new file mode 100644 index 000000000..d9efcd502 --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/PostResource.java @@ -0,0 +1,70 @@ +package cn.stylefeng.roses.kernel.resource.api.annotation; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.lang.annotation.*; + +/** + * 资源标识,此注解代替Spring Mvc的@PostMapping注解 + *

+ * 目的是为了在使用Spring Mvc的基础之上,增加对接口权限的控制功能 + * + * @author fengshuonan + * @date 2018-01-03-下午2:56 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping(method = RequestMethod.POST) +public @interface PostResource { + + /** + *

+     * 资源编码唯一标识.
+     *
+     * 说明:
+     *     1.可不填写此注解属性.
+     *     2.若不填写,则默认生成的编码标识为: 控制器类名称 + 分隔符 + 方法名称.
+     *     3.若编码存在重复则系统启动异常
+     *
+     * 
+ */ + String code() default ""; + + /** + * 资源名称(必填项) + */ + String name() default ""; + + /** + * 是否是菜单(true-是菜单标识,false-不是菜单标识) + */ + boolean menuFlag() default false; + + /** + * 需要登录(true-需要登录,false-不需要登录) + */ + boolean requiredLogin() default true; + + /** + * 需要鉴权(true-需要鉴权,false-不需要鉴权) + */ + boolean requiredPermission() default true; + + /** + * 资源的响应类型,用于生成api文档 + */ + Class responseClass() default Void.class; + + /** + * 请求路径(同RequestMapping) + */ + @AliasFor(annotation = RequestMapping.class) String[] path() default {}; + + /** + * 请求的http方法(同RequestMapping) + */ + @AliasFor(annotation = RequestMapping.class) RequestMethod[] method() default RequestMethod.POST; +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/field/ChineseDescription.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/field/ChineseDescription.java new file mode 100644 index 000000000..44046eb26 --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/annotation/field/ChineseDescription.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.resource.api.annotation.field; + +import java.lang.annotation.*; + +/** + * 加在字段上,描述字段的中文名称 + *

+ * 用来解决资源扫描时候,扫描的类的字段上的中文注释获取的问题 + * + * @author fengshuonan + * @date 2020/12/9 15:59 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ChineseDescription { + + /** + * 中文注释的值 + */ + String value() default ""; + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/constants/ScannerConstants.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/constants/ScannerConstants.java new file mode 100644 index 000000000..65adc99fe --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/constants/ScannerConstants.java @@ -0,0 +1,26 @@ +package cn.stylefeng.roses.kernel.resource.api.constants; + +/** + * 资源扫描模块的常量 + * + * @author fengshuonan + * @date 2020/11/3 13:50 + */ +public interface ScannerConstants { + + /** + * 资源模块的名称 + */ + String RESOURCE_MODULE_NAME = "kernel-d-scanner"; + + /** + * 异常枚举的步进值 + */ + String RESOURCE_EXCEPTION_STEP_CODE = "17"; + + /** + * 资源前缀标识 + */ + String RESOURCE_CACHE_KEY = "GUNS_RESOURCE_CACHES"; + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/exception/ScannerException.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/exception/ScannerException.java new file mode 100644 index 000000000..6803bcb23 --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/exception/ScannerException.java @@ -0,0 +1,27 @@ +package cn.stylefeng.roses.kernel.resource.api.exception; + +import cn.stylefeng.roses.kernel.resource.api.constants.ScannerConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; + +/** + * 资源模块的异常 + * + * @author fengshuonan + * @date 2020/11/3 13:54 + */ +public class ScannerException extends ServiceException { + + public ScannerException(String errorCode, String userTip) { + super(ScannerConstants.RESOURCE_MODULE_NAME, errorCode, userTip); + } + + public ScannerException(AbstractExceptionEnum exception) { + super(ScannerConstants.RESOURCE_MODULE_NAME, exception); + } + + public ScannerException(AbstractExceptionEnum exception, String userTip) { + super(ScannerConstants.RESOURCE_MODULE_NAME, exception.getErrorCode(), userTip); + } + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/exception/enums/ScannerExceptionEnum.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/exception/enums/ScannerExceptionEnum.java new file mode 100644 index 000000000..16623a232 --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/exception/enums/ScannerExceptionEnum.java @@ -0,0 +1,42 @@ +package cn.stylefeng.roses.kernel.resource.api.exception.enums; + +import cn.stylefeng.roses.kernel.resource.api.constants.ScannerConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import lombok.Getter; + +/** + * 资源相关的异常枚举 + * + * @author fengshuonan + * @date 2020/11/3 13:55 + */ +@Getter +public enum ScannerExceptionEnum implements AbstractExceptionEnum { + + /** + * 获取资源为空 + */ + RESOURCE_DEFINITION_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ScannerConstants.RESOURCE_MODULE_NAME + "01", "获取资源为空,请检查当前请求url是否存在对应的ResourceDefinition"), + + /** + * 扫描资源过程中,存在不合法控制器名称,请将控制名称以Controller结尾 + */ + ERROR_CONTROLLER_NAME(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ScannerConstants.RESOURCE_MODULE_NAME + "02", "扫描资源过程中,存在不合法控制器名称,请将控制名称以Controller结尾,控制器名称:{}"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + ScannerExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/holder/InitScanFlagHolder.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/holder/InitScanFlagHolder.java new file mode 100644 index 000000000..94f328365 --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/holder/InitScanFlagHolder.java @@ -0,0 +1,21 @@ +package cn.stylefeng.roses.kernel.resource.api.holder; + +/** + * 初始化标记,防止初始化多次 + * + * @author fengshuonan + * @date 2019-09-27-17:23 + */ +public class InitScanFlagHolder { + + private static Boolean INIT_MANAGER_FLAG = false; + + public static synchronized Boolean getFlag() { + return INIT_MANAGER_FLAG; + } + + public static synchronized void setFlag() { + INIT_MANAGER_FLAG = true; + } + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/holder/IpAddrHolder.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/holder/IpAddrHolder.java new file mode 100644 index 000000000..25893caa0 --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/holder/IpAddrHolder.java @@ -0,0 +1,27 @@ +package cn.stylefeng.roses.kernel.resource.api.holder; + +/** + * IP地址的临时存储 用在资源扫描 + *

+ * 获取ip地址的方法较慢,并且每个资源装配需要装填ip地址,所以用ThreadLocal临时缓存 + * + * @author fengshuonan + * @date 2020/10/19 21:15 + */ +public class IpAddrHolder { + + private static final ThreadLocal IP_ADDR_HOLDER = new ThreadLocal<>(); + + public static void set(String ip) { + IP_ADDR_HOLDER.set(ip); + } + + public static String get() { + return IP_ADDR_HOLDER.get(); + } + + public static void clear() { + IP_ADDR_HOLDER.remove(); + } + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/FieldDescription.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/FieldDescription.java new file mode 100644 index 000000000..6b13fc35b --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/FieldDescription.java @@ -0,0 +1,45 @@ +package cn.stylefeng.roses.kernel.resource.api.pojo.resource; + +import lombok.Data; + +import java.util.Map; +import java.util.Set; + +/** + * 字典描述类 + * + * @author fengshuonan + * @date 2020/12/8 18:25 + */ +@Data +public class FieldDescription { + + /** + * 字段中文名称,例如:创建用户 + */ + private String chineseName; + + /** + * 字段类型,例如:String + */ + private String fieldClassType; + + /** + * 字段名称,例如:createUser + */ + private String fieldName; + + /** + * 字段的注解,例如:NotBlack,NotNull + */ + private Set annotations; + + /** + * 按校验组分的注解集合 + *

+ * 例如: + * key = add, value = [NotBlank,TableUniqueValue] + */ + private Map> groupAnnotations; + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/ReportResourceParam.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/ReportResourceParam.java new file mode 100644 index 000000000..eaf7fa1c2 --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/ReportResourceParam.java @@ -0,0 +1,36 @@ +package cn.stylefeng.roses.kernel.resource.api.pojo.resource; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Map; + +/** + * 资源持久化的请求参数封装 + * + * @author fengshuonan + * @date 2020/10/19 21:55 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ReportResourceParam extends BaseRequest { + + /** + * 项目编码(如果您不设置的话,默认使用spring.application.name填充) + *

+ * 修复一个项目启动的时候会误删别的项目资源的问题 + */ + private String projectCode; + + /** + * 资源集合 + */ + private Map> resourceDefinitions; + + public ReportResourceParam(String projectCode, Map> resourceDefinitions) { + this.projectCode = projectCode; + this.resourceDefinitions = resourceDefinitions; + } + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/ResourceDefinition.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/ResourceDefinition.java new file mode 100644 index 000000000..35134a362 --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/ResourceDefinition.java @@ -0,0 +1,112 @@ +package cn.stylefeng.roses.kernel.resource.api.pojo.resource; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.Set; + +/** + * API资源的包装类 + * + * @author fengshuonan + * @date 2018-01-03-下午3:27 + */ +@Data +public class ResourceDefinition implements Serializable { + + /** + * 应用的标识 + */ + private String appCode; + + /** + * 项目编码(如果您不设置的话,默认使用spring.application.name填充) + *

+ * 修复一个项目启动的时候会误删别的项目资源的问题 + * + * @since 2.2.12 + */ + private String projectCode; + + /** + * 控制器类名称 + */ + private String className; + + /** + * 控制器中的方法名称 + */ + private String methodName; + + /** + * 资源所属模块 + */ + private String modularCode; + + /** + * 模块中文名称 + */ + private String modularName; + + /** + * 资源的标识 + */ + private String code; + + /** + * 资源名称 + */ + private String name; + + /** + * 资源的请求路径 + */ + private String url; + + /** + * http请求方法 + */ + private String httpMethod; + + /** + * 是否是菜单(true-是,false-否) + */ + private Boolean menuFlag; + + /** + * 是否需要登录 + */ + private Boolean requiredLogin; + + /** + * 是否需要鉴权 + */ + private Boolean requiredPermission; + + /** + * 资源添加日期 + */ + private Date createTime; + + /** + * 初始化资源的机器的ip地址 + */ + private String ipAddress; + + /** + * 需要进行参数校验的分组 + */ + private Set validateGroups; + + /** + * 接口参数的字段描述 + */ + private Set paramFieldDescriptions; + + /** + * 接口返回结果的字段描述 + */ + private Set responseFieldDescriptions; + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/ResourceUrlParam.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/ResourceUrlParam.java new file mode 100644 index 000000000..ac093e5ed --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/ResourceUrlParam.java @@ -0,0 +1,19 @@ +package cn.stylefeng.roses.kernel.resource.api.pojo.resource; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 获取资源通过url请求 + * + * @author fengshuonan + * @date 2020/10/19 22:05 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ResourceUrlParam extends BaseRequest { + + private String url; + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/UserResourceParam.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/UserResourceParam.java new file mode 100644 index 000000000..9bd4d99e8 --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/resource/UserResourceParam.java @@ -0,0 +1,22 @@ +package cn.stylefeng.roses.kernel.resource.api.pojo.resource; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 获取用户资源请求 + * + * @author fengshuonan + * @date 2020/10/19 22:04 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class UserResourceParam extends BaseRequest { + + /** + * 用户id + */ + private String userId; + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/scanner/ScannerProperties.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/scanner/ScannerProperties.java new file mode 100644 index 000000000..02cf80db8 --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/pojo/scanner/ScannerProperties.java @@ -0,0 +1,43 @@ +package cn.stylefeng.roses.kernel.resource.api.pojo.scanner; + +import lombok.Data; + +/** + * 扫描的常量 + * + * @author fengshuonan + * @date 2018-01-03 21:39 + */ +@Data +public class ScannerProperties { + + /** + * 资源扫描开关 + */ + private Boolean open; + + /** + * 被扫描应用的名称 + */ + private String appName; + + /** + * 应用的编码 + */ + private String appCode; + + /** + * 链接符号 + */ + private String linkSymbol = "$"; + + /** + * 项目编码(如果您不设置的话,默认使用spring.application.name填充,请不要设置此值,这个值和网关资源过滤有关) + *

+ * 修复一个项目启动的时候会误删别的项目资源的问题 + * + * @since 2.2.12 + */ + private String projectCode; + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/util/ClassReflectUtil.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/util/ClassReflectUtil.java new file mode 100644 index 000000000..4cca334bf --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/util/ClassReflectUtil.java @@ -0,0 +1,146 @@ +package cn.stylefeng.roses.kernel.resource.api.util; + +import cn.hutool.core.util.ClassUtil; +import cn.stylefeng.roses.kernel.resource.api.annotation.field.ChineseDescription; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.FieldDescription; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * 类的反射工具 + * + * @author fengshuonan + * @date 2020/12/8 18:23 + */ +public class ClassReflectUtil { + + /** + * 获取一个类的所有字段描述 + * + * @param clazz 类的class类型 + * @return 该类下所有字段的描述信息 + * @author fengshuonan + * @date 2020/12/8 18:27 + */ + public static Set getClassFieldDescription(Class clazz) { + + HashSet fieldDescriptions = new HashSet<>(); + + if (clazz == null) { + return fieldDescriptions; + } + + // 获取类中的所有字段 + Field[] declaredFields = ClassUtil.getDeclaredFields(clazz); + + for (Field declaredField : declaredFields) { + + FieldDescription fieldDescription = new FieldDescription(); + + // 获取字段的名称 + String name = declaredField.getName(); + fieldDescription.setFieldName(name); + + // 获取字段的类型 + Class declaredFieldType = declaredField.getType(); + fieldDescription.setFieldClassType(declaredFieldType.getSimpleName()); + + // 获取字段的所有注解 + Annotation[] annotations = declaredField.getAnnotations(); + if (annotations != null && annotations.length > 0) { + + // 设置字段的所有注解 + fieldDescription.setAnnotations(annotationsToStrings(annotations)); + + // 遍历字段上的所有注解,找到带groups属性的,按group分类组装注解 + Map> groupAnnotations = new HashMap<>(); + for (Annotation annotation : annotations) { + Class[] validateGroupsClasses = invokeAnnotationMethodIgnoreError(annotation, "groups", Class[].class); + if (validateGroupsClasses != null) { + for (Class validateGroupsClass : validateGroupsClasses) { + addGroupValidateAnnotation(annotation, validateGroupsClass, groupAnnotations); + } + } + } + // 设置分组注解 + fieldDescription.setGroupAnnotations(groupAnnotations); + + // 填充字段的中文名称 + ChineseDescription chineseDescription = declaredField.getAnnotation(ChineseDescription.class); + if (chineseDescription != null) { + fieldDescription.setChineseName(chineseDescription.value()); + } + } + + fieldDescriptions.add(fieldDescription); + } + + return fieldDescriptions; + } + + /** + * 调用注解上的某个方法,并获取结果,忽略异常 + * + * @author fengshuonan + * @date 2020/12/8 17:13 + */ + private static T invokeAnnotationMethodIgnoreError(Annotation apiResource, String methodName, Class resultType) { + try { + Class annotationType = apiResource.annotationType(); + Method method = annotationType.getMethod(methodName); + return (T)method.invoke(apiResource); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + // 忽略异常 + } + return null; + } + + /** + * 将字段上的分组注解添加到对应的组中 + * + * @param fieldAnnotation 字段上的注解 + * @param validateGroupsClass 校验分组 + * @param groupAnnotations 分组注解集合 + * @author fengshuonan + * @date 2020/12/8 19:12 + */ + private static void addGroupValidateAnnotation(Annotation fieldAnnotation, Class validateGroupsClass, Map> groupAnnotations) { + Set annotations = groupAnnotations.get(validateGroupsClass.getSimpleName()); + if (annotations == null) { + annotations = new HashSet<>(); + } + annotations.add(fieldAnnotation.annotationType().getSimpleName()); + groupAnnotations.put(validateGroupsClass.getSimpleName(), annotations); + } + + /** + * 注解转化为string名称 + *

+ * 例如: + * NotBlack注解 > NotBlack + * + * @author fengshuonan + * @date 2020/12/9 13:39 + */ + private static Set annotationsToStrings(Annotation[] annotations) { + Set strings = new HashSet<>(); + + if (annotations == null || annotations.length == 0) { + return strings; + } + + for (Annotation annotation : annotations) { + strings.add(annotation.annotationType().getSimpleName()); + } + + return strings; + } + +} diff --git a/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/util/MethodReflectUtil.java b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/util/MethodReflectUtil.java new file mode 100644 index 000000000..8915527a1 --- /dev/null +++ b/kernel-d-scanner/scanner-api/src/main/java/cn/stylefeng/roses/kernel/resource/api/util/MethodReflectUtil.java @@ -0,0 +1,141 @@ +package cn.stylefeng.roses.kernel.resource.api.util; + +import cn.hutool.core.collection.CollectionUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 反射工具类,获取方法的一些元数据 + * + * @author fengshuonan + * @date 2020/12/8 17:48 + */ +@Slf4j +public class MethodReflectUtil { + + /** + * 获取方法上的注解 + *

+ * 注意,此方法只获取方法第一个参数的所有注解 + * + * @param method 方法反射信息 + * @return 方法参数上的注解集合 + * @author fengshuonan + * @date 2020/12/8 17:49 + */ + public static List getMethodFirstParamAnnotations(Method method) { + if (method == null) { + return null; + } + + if (method.getParameterCount() <= 0) { + return null; + } + + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + if (parameterAnnotations.length == 0) { + return null; + } + + // 只获取第一个参数的所有注解,所以下标为0 + Annotation[] resultAnnotations = parameterAnnotations[0]; + if (resultAnnotations == null || resultAnnotations.length == 0) { + return null; + } else { + return CollectionUtil.toList(resultAnnotations); + } + } + + /** + * 获取方法上校验分组 + *

+ * 例如:获取如下方法的校验分组信息SysAppRequest.edit.class + *

+     * public ResponseData edit(@RequestBody @Validated(SysAppRequest.edit.class) SysAppRequest sysAppParam) {
+     *     ...
+     * }
+     * 
+ * + * @param method 方法反射信息 + * @return 方法的参数校验分组信息 + * @author fengshuonan + * @date 2020/12/8 17:59 + */ + public static Set getMethodValidateGroup(Method method) { + List methodFirstParamAnnotations = getMethodFirstParamAnnotations(method); + if (methodFirstParamAnnotations == null) { + return null; + } + + // 判断annotation有没有是@Validated注解类型的 + try { + for (Annotation annotation : methodFirstParamAnnotations) { + if (Validated.class.equals(annotation.annotationType())) { + Method validateGroupMethod = annotation.annotationType().getMethod("value"); + Object invoke = validateGroupMethod.invoke(annotation); + if (invoke != null) { + Class[] result = (Class[])invoke; + if (result.length > 0) { + HashSet groupClassNames = new HashSet<>(); + for (Class groupClass : result) { + groupClassNames.add(groupClass.getSimpleName()); + } + return groupClassNames; + } + } + } + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + log.error("获取方法上的校验分组出错!", e); + } + return null; + } + + /** + * 获取方法第一个参数的类类型 + * + * @param method 方法反射信息 + * @return 方法第一个参数的class类型 + * @author fengshuonan + * @date 2020/12/8 18:16 + */ + public static Class getMethodFirstParamClass(Method method) { + if (method == null) { + return null; + } + + if (method.getParameterCount() <= 0) { + return null; + } + + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length > 0) { + return parameterTypes[0]; + } else { + return null; + } + } + + /** + * 获取方法的返回值class类型 + * + * @param method 方法反射信息 + * @return 方法返回值的class类型 + * @author fengshuonan + * @date 2020/12/8 18:20 + */ + public static Class getMethodReturnClass(Method method) { + if (method == null) { + return null; + } + return method.getReturnType(); + } + +} diff --git a/kernel-d-scanner/scanner-sdk-scanner/README.md b/kernel-d-scanner/scanner-sdk-scanner/README.md new file mode 100644 index 000000000..791828d9b --- /dev/null +++ b/kernel-d-scanner/scanner-sdk-scanner/README.md @@ -0,0 +1 @@ +资源的sdk模块,提供资源扫描,资源初始化器,资源工厂等等 \ No newline at end of file diff --git a/kernel-d-scanner/scanner-sdk-scanner/pom.xml b/kernel-d-scanner/scanner-sdk-scanner/pom.xml new file mode 100644 index 000000000..6660a96a4 --- /dev/null +++ b/kernel-d-scanner/scanner-sdk-scanner/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-scanner + 1.0.0 + ../pom.xml + + + scanner-sdk-scanner + + jar + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + org.springframework + spring-web + + + + + diff --git a/kernel-d-scanner/scanner-sdk-scanner/src/main/java/cn/stylefeng/roses/kernel/resource/scanner/ApiResourceScanner.java b/kernel-d-scanner/scanner-sdk-scanner/src/main/java/cn/stylefeng/roses/kernel/resource/scanner/ApiResourceScanner.java new file mode 100644 index 000000000..d04c10c84 --- /dev/null +++ b/kernel-d-scanner/scanner-sdk-scanner/src/main/java/cn/stylefeng/roses/kernel/resource/scanner/ApiResourceScanner.java @@ -0,0 +1,321 @@ +package cn.stylefeng.roses.kernel.resource.scanner; + +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.resource.api.ResourceCollectorApi; +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.resource.api.exception.ScannerException; +import cn.stylefeng.roses.kernel.resource.api.holder.IpAddrHolder; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceDefinition; +import cn.stylefeng.roses.kernel.resource.api.pojo.scanner.ScannerProperties; +import cn.stylefeng.roses.kernel.resource.api.util.ClassReflectUtil; +import cn.stylefeng.roses.kernel.resource.api.util.MethodReflectUtil; +import cn.stylefeng.roses.kernel.rule.util.AopTargetUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import static cn.stylefeng.roses.kernel.resource.api.exception.enums.ScannerExceptionEnum.ERROR_CONTROLLER_NAME; + +/** + * 资源扫描器,扫描控制器上的@ApiResource,@GetResource,@PostResource + * + * @author fengshuonan + * @date 2020/10/19 22:31 + */ +@Slf4j +public class ApiResourceScanner implements BeanPostProcessor { + + /** + * 权限资源收集接口 + */ + private final ResourceCollectorApi resourceCollectorApi; + + /** + * 项目的配置信息 + */ + private final ScannerProperties scannerProperties; + + /** + * 项目名称 + */ + private final String springApplicationName; + + public ApiResourceScanner(ResourceCollectorApi resourceCollectorApi, ScannerProperties scannerProperties, String springApplicationName) { + this.resourceCollectorApi = resourceCollectorApi; + this.scannerProperties = scannerProperties; + this.springApplicationName = springApplicationName; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + + // 如果controller是代理对象,则需要获取原始类的信息 + Object aopTarget = AopTargetUtils.getTarget(bean); + + if (aopTarget == null) { + aopTarget = bean; + } + + Class clazz = aopTarget.getClass(); + + // 判断是不是控制器,不是控制器就略过 + boolean controllerFlag = getControllerFlag(clazz); + if (!controllerFlag) { + return bean; + } + + // 扫描控制器的所有带ApiResource注解的方法 + List apiResources = doScan(clazz); + + // 将扫描到的注解转化为资源实体存储到缓存 + persistApiResources(apiResources); + + return bean; + } + + /** + * 判断一个类是否是控制器 + * + * @author fengshuonan + * @date 2020/12/9 11:21 + */ + private boolean getControllerFlag(Class clazz) { + Annotation[] annotations = clazz.getAnnotations(); + for (Annotation annotation : annotations) { + if (RestController.class.equals(annotation.annotationType()) || Controller.class.equals(annotation.annotationType())) { + return true; + } + } + return false; + } + + /** + * 扫描整个类中包含的所有@ApiResource资源 + * + * @author fengshuonan + * @date 2020/12/9 11:21 + */ + private List doScan(Class clazz) { + // 绑定类的code-中文名称映射 + ApiResource classApiAnnotation = clazz.getAnnotation(ApiResource.class); + if (classApiAnnotation != null) { + if (StrUtil.isEmpty(classApiAnnotation.code())) { + String className = clazz.getSimpleName(); + int controllerIndex = className.indexOf("Controller"); + if (controllerIndex == -1) { + throw new IllegalArgumentException("controller class name is not illegal, it should end with Controller!"); + } + String code = className.substring(0, controllerIndex); + this.resourceCollectorApi.bindResourceName(StrUtil.toUnderlineCase(code), classApiAnnotation.name()); + } else { + this.resourceCollectorApi.bindResourceName(StrUtil.toUnderlineCase(classApiAnnotation.code()), classApiAnnotation.name()); + } + } + + ArrayList apiResources = new ArrayList<>(); + Method[] declaredMethods = clazz.getDeclaredMethods(); + if (declaredMethods.length > 0) { + for (Method declaredMethod : declaredMethods) { + ApiResource apiResource = declaredMethod.getAnnotation(ApiResource.class); + GetResource getResource = declaredMethod.getAnnotation(GetResource.class); + PostResource postResource = declaredMethod.getAnnotation(PostResource.class); + + Annotation annotation = null; + if (apiResource != null) { + annotation = apiResource; + } + + if (getResource != null) { + annotation = getResource; + } + + if (postResource != null) { + annotation = postResource; + } + + if (annotation != null) { + ResourceDefinition definition = createDefinition(clazz, declaredMethod, annotation); + apiResources.add(definition); + log.debug("扫描到资源: " + definition); + } + } + } + return apiResources; + } + + /** + * 缓存扫描到的api资源 + * + * @author fengshuonan + * @date 2020/12/9 11:22 + */ + private void persistApiResources(List apiResources) { + resourceCollectorApi.collectResources(apiResources); + } + + /** + * 根据类信息,方法信息,注解信息创建ResourceDefinition对象 + * + * @author fengshuonan + * @date 2020/12/9 11:22 + */ + private ResourceDefinition createDefinition(Class clazz, Method method, Annotation apiResource) { + ResourceDefinition resourceDefinition = new ResourceDefinition(); + + // 填充控制器类的名称 + resourceDefinition.setClassName(clazz.getSimpleName()); + + // 填充方法名称 + resourceDefinition.setMethodName(method.getName()); + + // 填充模块编码,模块编码就是控制器名称截取Controller关键字前边的字符串 + String className = resourceDefinition.getClassName(); + int controllerIndex = className.indexOf("Controller"); + if (controllerIndex == -1) { + String userTip = StrUtil.format(ERROR_CONTROLLER_NAME.getUserTip(), clazz.getName()); + throw new ScannerException(ERROR_CONTROLLER_NAME, userTip); + } + String modular = className.substring(0, controllerIndex); + resourceDefinition.setModularCode(modular); + + // 填充模块的中文名称 + ApiResource classApiAnnotation = clazz.getAnnotation(ApiResource.class); + resourceDefinition.setModularName(classApiAnnotation.name()); + + // 如果控制器类上标识了appCode则应用标识上的appCode,如果控制器上没标识则用配置文件中的appCode + if (StrUtil.isNotBlank(classApiAnnotation.appCode())) { + resourceDefinition.setAppCode(classApiAnnotation.appCode()); + } else { + resourceDefinition.setAppCode(scannerProperties.getAppCode()); + } + + // 如果没有填写code则用"模块名称_方法名称"为默认的标识 + String code = invokeAnnotationMethod(apiResource, "code", String.class); + if (StrUtil.isEmpty(code)) { + resourceDefinition.setCode(resourceDefinition.getAppCode() + scannerProperties.getLinkSymbol() + StrUtil.toUnderlineCase(modular) + scannerProperties.getLinkSymbol() + StrUtil.toUnderlineCase(method.getName())); + } else { + resourceDefinition.setCode(resourceDefinition.getAppCode() + scannerProperties.getLinkSymbol() + StrUtil.toUnderlineCase(modular) + scannerProperties.getLinkSymbol() + StrUtil.toUnderlineCase(code)); + } + + // 填充其他属性 + String name = invokeAnnotationMethod(apiResource, "name", String.class); + String[] path = invokeAnnotationMethod(apiResource, "path", String[].class); + RequestMethod[] requestMethods = invokeAnnotationMethod(apiResource, "method", RequestMethod[].class); + Boolean menuFlag = invokeAnnotationMethod(apiResource, "menuFlag", Boolean.class); + Boolean requiredLogin = invokeAnnotationMethod(apiResource, "requiredLogin", Boolean.class); + Boolean requiredPermission = invokeAnnotationMethod(apiResource, "requiredPermission", Boolean.class); + + resourceDefinition.setRequiredLogin(requiredLogin); + resourceDefinition.setRequiredPermission(requiredPermission); + resourceDefinition.setMenuFlag(menuFlag); + resourceDefinition.setName(name); + resourceDefinition.setUrl(getControllerClassRequestPath(clazz) + path[0]); + StringBuilder methodNames = new StringBuilder(); + for (RequestMethod requestMethod : requestMethods) { + methodNames.append(requestMethod.name()).append(","); + } + resourceDefinition.setHttpMethod(StrUtil.removeSuffix(methodNames.toString(), ",")); + + // 填充ip地址 + String localMacAddress = IpAddrHolder.get(); + if (localMacAddress == null) { + try { + localMacAddress = NetUtil.getLocalhostStr(); + IpAddrHolder.set(localMacAddress); + } catch (UtilException e) { + log.error("获取当前机器ip地址错误!"); + } + } + + resourceDefinition.setIpAddress(localMacAddress == null ? "" : localMacAddress); + resourceDefinition.setCreateTime(new Date()); + + // 填充项目编码 + resourceDefinition.setProjectCode(scannerProperties.getProjectCode()); + + // 填充方法的校验分组 + Set methodValidateGroup = MethodReflectUtil.getMethodValidateGroup(method); + if (methodValidateGroup != null) { + resourceDefinition.setValidateGroups(methodValidateGroup); + } + + // 填充方法返回结果字段的详细信息 + // @ApiResource注解上标识了responseClass属性,则用responseClass的值为准,否则按真实方法的返回值class + Class responseClass = invokeAnnotationMethod(apiResource, "responseClass", Class.class); + if (!Void.class.equals(responseClass)) { + resourceDefinition.setResponseFieldDescriptions(ClassReflectUtil.getClassFieldDescription(responseClass)); + } else { + Class methodReturnClass = MethodReflectUtil.getMethodReturnClass(method); + resourceDefinition.setResponseFieldDescriptions(ClassReflectUtil.getClassFieldDescription(methodReturnClass)); + } + + // 填充方法的请求参数字段的详细信息 + Class firstParamClass = MethodReflectUtil.getMethodFirstParamClass(method); + resourceDefinition.setParamFieldDescriptions(ClassReflectUtil.getClassFieldDescription(firstParamClass)); + + return resourceDefinition; + } + + /** + * 获取控制器类上的RequestMapping注解的映射路径,用于拼接path + *

+ * 2018年5月2日修改,控制器路径前加上spring.application.name + */ + private String getControllerClassRequestPath(Class clazz) { + String result; + + ApiResource controllerRequestMapping = clazz.getDeclaredAnnotation(ApiResource.class); + if (controllerRequestMapping == null) { + result = ""; + } else { + String[] paths = controllerRequestMapping.path(); + if (paths.length > 0) { + result = paths[0]; + } else { + result = ""; + } + } + + // 拼接spring.application.name + result = "/" + springApplicationName + result; + return result; + } + + /** + * 调用注解上的某个方法,并获取结果 + * + * @author fengshuonan + * @date 2020/12/8 17:13 + */ + private T invokeAnnotationMethod(Annotation apiResource, String methodName, Class resultType) { + try { + Class annotationType = apiResource.annotationType(); + Method method = annotationType.getMethod(methodName); + return (T)method.invoke(apiResource); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + log.error("扫描api资源时出错!", e); + } + throw new RuntimeException("扫描api资源时出错!"); + } + +} diff --git a/kernel-d-scanner/scanner-sdk-scanner/src/main/java/cn/stylefeng/roses/kernel/resource/scanner/DefaultResourceCollector.java b/kernel-d-scanner/scanner-sdk-scanner/src/main/java/cn/stylefeng/roses/kernel/resource/scanner/DefaultResourceCollector.java new file mode 100644 index 000000000..b377d89a8 --- /dev/null +++ b/kernel-d-scanner/scanner-sdk-scanner/src/main/java/cn/stylefeng/roses/kernel/resource/scanner/DefaultResourceCollector.java @@ -0,0 +1,121 @@ +package cn.stylefeng.roses.kernel.resource.scanner; + +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.resource.api.ResourceCollectorApi; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceDefinition; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 默认的资源收集器,默认搜集到内存Map中 + * + * @author fengshuonan + * @date 2020/10/19 20:33 + */ +public class DefaultResourceCollector implements ResourceCollectorApi { + + /** + * 以资源编码为key,存放资源集合 + */ + private final Map resourceDefinitions = new ConcurrentHashMap<>(); + + /** + * 第一个key以模块编码(控制器名称下划线分割),第二个key是资源编码,存放资源集合 + */ + private final Map> modularResourceDefinitions = new ConcurrentHashMap<>(); + + /** + * key以模块编码(控制器名称下划线分割),value是控制器的中文名称 + */ + private final Map controllerCodeNameDict = new HashMap<>(); + + /** + * key是请求的url,value是资源信息 + */ + private final Map urlDefineResources = new ConcurrentHashMap<>(); + + @Override + public void collectResources(List apiResource) { + if (apiResource != null && apiResource.size() > 0) { + for (ResourceDefinition resourceDefinition : apiResource) { + ResourceDefinition alreadyFlag = resourceDefinitions.get(resourceDefinition.getCode()); + if (alreadyFlag != null) { + throw new RuntimeException("资源扫描过程中存在重复资源!\n已存在资源:" + alreadyFlag + "\n新资源为: " + resourceDefinition); + } + resourceDefinitions.put(resourceDefinition.getCode(), resourceDefinition); + urlDefineResources.put(resourceDefinition.getUrl(), resourceDefinition); + + // 存储模块资源 + Map modularResources = modularResourceDefinitions.get(StrUtil.toUnderlineCase(resourceDefinition.getModularCode())); + if (modularResources == null) { + modularResources = new HashMap<>(); + modularResources.put(resourceDefinition.getCode(), resourceDefinition); + modularResourceDefinitions.put(StrUtil.toUnderlineCase(resourceDefinition.getModularCode()), modularResources); + } else { + modularResources.put(resourceDefinition.getCode(), resourceDefinition); + } + + // 添加资源code-中文名称字典 + this.bindResourceName(resourceDefinition.getCode(), resourceDefinition.getName()); + } + } + } + + @Override + public ResourceDefinition getResource(String resourceCode) { + return resourceDefinitions.get(resourceCode); + } + + @Override + public List getAllResources() { + Set> entries = resourceDefinitions.entrySet(); + ArrayList resourceDefinitions = new ArrayList<>(); + for (Map.Entry entry : entries) { + resourceDefinitions.add(entry.getValue()); + } + return resourceDefinitions; + } + + @Override + public List getResourcesByModularCode(String code) { + Map stringResourceDefinitionMap = modularResourceDefinitions.get(code); + ArrayList resourceDefinitions = new ArrayList<>(); + for (String key : stringResourceDefinitionMap.keySet()) { + ResourceDefinition resourceDefinition = stringResourceDefinitionMap.get(key); + resourceDefinitions.add(resourceDefinition); + } + return resourceDefinitions; + } + + @Override + public String getResourceName(String code) { + return controllerCodeNameDict.get(code); + } + + @Override + public void bindResourceName(String code, String name) { + controllerCodeNameDict.putIfAbsent(code, name); + } + + @Override + public Map> getModularResources() { + return this.modularResourceDefinitions; + } + + @Override + public String getResourceUrl(String code) { + ResourceDefinition resourceDefinition = this.resourceDefinitions.get(code); + if (resourceDefinition == null) { + return null; + } else { + return resourceDefinition.getUrl(); + } + } + + @Override + public ResourceDefinition getResourceByUrl(String url) { + return this.urlDefineResources.get(url); + } + +} diff --git a/kernel-d-scanner/scanner-sdk-scanner/src/main/java/cn/stylefeng/roses/kernel/resource/scanner/ResourceReportListener.java b/kernel-d-scanner/scanner-sdk-scanner/src/main/java/cn/stylefeng/roses/kernel/resource/scanner/ResourceReportListener.java new file mode 100644 index 000000000..610aa5137 --- /dev/null +++ b/kernel-d-scanner/scanner-sdk-scanner/src/main/java/cn/stylefeng/roses/kernel/resource/scanner/ResourceReportListener.java @@ -0,0 +1,58 @@ +package cn.stylefeng.roses.kernel.resource.scanner; + +import cn.stylefeng.roses.kernel.resource.api.ResourceCollectorApi; +import cn.stylefeng.roses.kernel.resource.api.ResourceReportApi; +import cn.stylefeng.roses.kernel.resource.api.holder.InitScanFlagHolder; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ReportResourceParam; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceDefinition; +import cn.stylefeng.roses.kernel.resource.api.pojo.scanner.ScannerProperties; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import java.util.Map; + +/** + * 监听项目初始化完毕,汇报资源到服务(可为远程服务,可为本服务) + * + * @author fengshuonan + * @date 2020/10/19 22:27 + */ +@Slf4j +public class ResourceReportListener implements ApplicationListener { + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + + // 如果是配置中心的上下文略过,spring cloud环境environment会读取不到 + ConfigurableApplicationContext applicationContext = event.getApplicationContext(); + if (applicationContext instanceof AnnotationConfigApplicationContext) { + return; + } + + // 获取有没有开资源扫描开关 + ScannerProperties scannerProperties = applicationContext.getBean(ScannerProperties.class); + if (!scannerProperties.getOpen()) { + return; + } + + // 如果项目还没进行资源扫描 + if (!InitScanFlagHolder.getFlag()) { + + // 获取当前系统的所有资源 + ResourceCollectorApi resourceCollectorApi = applicationContext.getBean(ResourceCollectorApi.class); + Map> modularResources = resourceCollectorApi.getModularResources(); + + // 持久化资源,发送资源到资源服务或本项目(单体项目) + ResourceReportApi resourceService = applicationContext.getBean(ResourceReportApi.class); + resourceService.reportResources(new ReportResourceParam(scannerProperties.getProjectCode(), modularResources)); + + // 设置标识已经扫描过 + InitScanFlagHolder.setFlag(); + } + + } + +} diff --git a/kernel-d-scanner/scanner-spring-boot-starter/README.md b/kernel-d-scanner/scanner-spring-boot-starter/README.md new file mode 100644 index 000000000..4b572643b --- /dev/null +++ b/kernel-d-scanner/scanner-spring-boot-starter/README.md @@ -0,0 +1 @@ +资源扫描的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-scanner/scanner-spring-boot-starter/pom.xml b/kernel-d-scanner/scanner-spring-boot-starter/pom.xml new file mode 100644 index 000000000..e4d790bd5 --- /dev/null +++ b/kernel-d-scanner/scanner-spring-boot-starter/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-scanner + 1.0.0 + ../pom.xml + + + scanner-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + scanner-sdk-scanner + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-scanner/scanner-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/resource/starter/GunsResourceAutoConfiguration.java b/kernel-d-scanner/scanner-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/resource/starter/GunsResourceAutoConfiguration.java new file mode 100644 index 000000000..6fcd8c0c6 --- /dev/null +++ b/kernel-d-scanner/scanner-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/resource/starter/GunsResourceAutoConfiguration.java @@ -0,0 +1,70 @@ +package cn.stylefeng.roses.kernel.resource.starter; + +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.resource.api.ResourceCollectorApi; +import cn.stylefeng.roses.kernel.resource.api.pojo.scanner.ScannerProperties; +import cn.stylefeng.roses.kernel.resource.scanner.ApiResourceScanner; +import cn.stylefeng.roses.kernel.resource.scanner.DefaultResourceCollector; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 资源的自动配置 + * + * @author fengshuonan + * @date 2020/12/1 17:24 + */ +@Configuration +public class GunsResourceAutoConfiguration { + + public static final String SCANNER_PREFIX = "scanner"; + + @Value("${spring.application.name}") + private String springApplicationName; + + /** + * 资源扫描器的配置 + * + * @author fengshuonan + * @date 2020/12/3 17:54 + */ + @Bean + @ConfigurationProperties(prefix = SCANNER_PREFIX) + public ScannerProperties scannerProperties() { + return new ScannerProperties(); + } + + /** + * 资源扫描器 + * + * @author fengshuonan + * @date 2020/12/1 17:29 + */ + @Bean + @ConditionalOnMissingBean(ApiResourceScanner.class) + @ConditionalOnProperty(prefix = GunsResourceAutoConfiguration.SCANNER_PREFIX, name = "open", havingValue = "true") + public ApiResourceScanner apiResourceScanner(ResourceCollectorApi resourceCollectorApi, ScannerProperties scannerProperties) { + if (StrUtil.isBlank(scannerProperties.getProjectCode())) { + scannerProperties.setProjectCode(springApplicationName); + } + return new ApiResourceScanner(resourceCollectorApi, scannerProperties, scannerProperties.getAppCode()); + } + + /** + * 资源搜集器 + * + * @author fengshuonan + * @date 2020/12/1 17:29 + */ + @Bean + @ConditionalOnMissingBean(ResourceCollectorApi.class) + @ConditionalOnProperty(prefix = GunsResourceAutoConfiguration.SCANNER_PREFIX, name = "open", havingValue = "true") + public ResourceCollectorApi resourceCollectorApi() { + return new DefaultResourceCollector(); + } + +} diff --git a/kernel-d-scanner/scanner-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-scanner/scanner-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..c010629bc --- /dev/null +++ b/kernel-d-scanner/scanner-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.resource.starter.GunsResourceAutoConfiguration +org.springframework.context.ApplicationListener=\ + cn.stylefeng.roses.kernel.resource.scanner.ResourceReportListener \ No newline at end of file diff --git a/kernel-d-sms/README.md b/kernel-d-sms/README.md new file mode 100644 index 000000000..11a9f4bae --- /dev/null +++ b/kernel-d-sms/README.md @@ -0,0 +1 @@ +短信发送模块 \ No newline at end of file diff --git a/kernel-d-sms/pom.xml b/kernel-d-sms/pom.xml new file mode 100644 index 000000000..70d03089e --- /dev/null +++ b/kernel-d-sms/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-sms + + pom + + + sms-api + sms-sdk-aliyun + sms-sdk-tencent + sms-business-validation + sms-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-sms/sms-api/README.md b/kernel-d-sms/sms-api/README.md new file mode 100644 index 000000000..66ee229f9 --- /dev/null +++ b/kernel-d-sms/sms-api/README.md @@ -0,0 +1 @@ +短信模块的api \ No newline at end of file diff --git a/kernel-d-sms/sms-api/pom.xml b/kernel-d-sms/sms-api/pom.xml new file mode 100644 index 000000000..b1d3d8fbd --- /dev/null +++ b/kernel-d-sms/sms-api/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-sms + 1.0.0 + ../pom.xml + + + sms-api + + jar + + + + + + cn.stylefeng.roses + config-api + 1.0.0 + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/SmsSenderApi.java b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/SmsSenderApi.java new file mode 100644 index 000000000..040c623d7 --- /dev/null +++ b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/SmsSenderApi.java @@ -0,0 +1,26 @@ +package cn.stylefeng.roses.kernel.sms.api; + +import java.util.Map; + +/** + * 短信发送服务 + * + * @author fengshuonan + * @date 2018-07-06-下午2:14 + */ +public interface SmsSenderApi { + + /** + * 发送短信 + *

+ * 如果是腾讯云,params要用LinkedHashMap,保证顺序 + * + * @param phone 电话号码 + * @param templateCode 模板号码 + * @param params 模板里参数的集合 + * @author fengshuonan + * @date 2018/7/6 下午2:32 + */ + void sendSms(String phone, String templateCode, Map params); + +} diff --git a/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/constants/SmsConstants.java b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/constants/SmsConstants.java new file mode 100644 index 000000000..7c614f147 --- /dev/null +++ b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/constants/SmsConstants.java @@ -0,0 +1,28 @@ +package cn.stylefeng.roses.kernel.sms.api.constants; + +/** + * 短信模块的常量 + * + * @author fengshuonan + * @date 2020/10/26 15:42 + */ +public interface SmsConstants { + + /** + * 短信模块的名称 + */ + String SMS_MODULE_NAME = "kernel-d-sms"; + + /** + * 异常枚举的步进值 + */ + String SMS_EXCEPTION_STEP_CODE = "10"; + + /** + * 发送校验类短信 + *

+ * 系统会默认往短信参数中添加参数名为本常量的一个属性,用于放短信验证码 + */ + String SMS_CODE_PARAM_NAME = "code"; + +} diff --git a/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/context/SmsContext.java b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/context/SmsContext.java new file mode 100644 index 000000000..9a64b7eaa --- /dev/null +++ b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/context/SmsContext.java @@ -0,0 +1,24 @@ +package cn.stylefeng.roses.kernel.sms.api.context; + +import cn.hutool.extra.spring.SpringUtil; +import cn.stylefeng.roses.kernel.sms.api.SmsSenderApi; + +/** + * 短信发送类快速获取 + * + * @author fengshuonan + * @date 2020/10/26 16:53 + */ +public class SmsContext { + + /** + * 获取短信发送接口 + * + * @author fengshuonan + * @date 2020/10/26 16:54 + */ + public static SmsSenderApi me() { + return SpringUtil.getBean(SmsSenderApi.class); + } + +} diff --git a/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/exception/SmsException.java b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/exception/SmsException.java new file mode 100644 index 000000000..937ef94a0 --- /dev/null +++ b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/exception/SmsException.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.sms.api.exception; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; +import cn.stylefeng.roses.kernel.sms.api.constants.SmsConstants; + +/** + * 短信发送的异常 + * + * @author fengshuonan + * @date 2020/10/26 16:53 + */ +public class SmsException extends ServiceException { + + public SmsException(String errorCode, String userTip) { + super(SmsConstants.SMS_MODULE_NAME, errorCode, userTip); + } + + public SmsException(AbstractExceptionEnum exception) { + super(SmsConstants.SMS_MODULE_NAME, exception); + } + +} diff --git a/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/exception/enums/SmsExceptionEnum.java b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/exception/enums/SmsExceptionEnum.java new file mode 100644 index 000000000..e7f4a3eb1 --- /dev/null +++ b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/exception/enums/SmsExceptionEnum.java @@ -0,0 +1,72 @@ +package cn.stylefeng.roses.kernel.sms.api.exception.enums; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import cn.stylefeng.roses.kernel.sms.api.constants.SmsConstants; +import lombok.Getter; + +/** + * 短信发送的异常枚举 + * + * @author fengshuonan + * @date 2020/10/26 17:19 + */ +@Getter +public enum SmsExceptionEnum implements AbstractExceptionEnum { + + /** + * 阿里云短信发送异常 + */ + ALIYUN_SMS_ERROR(RuleConstants.THIRD_ERROR_TYPE_CODE + SmsConstants.SMS_EXCEPTION_STEP_CODE + "01", "阿里云短信发送异常,错误码:{},错误信息:{}"), + + /** + * 阿里云短信发送accesskey错误 + */ + ALIYUN_SMS_KEY_ERROR(RuleConstants.THIRD_ERROR_TYPE_CODE + SmsConstants.SMS_EXCEPTION_STEP_CODE + "02", "初始化sms客户端错误,accessKey错误,accessKeyId:{}"), + + /** + * 短信发送请求参数为空 + */ + SEND_SMS_PARAM_NULL(RuleConstants.BUSINESS_ERROR_TYPE_CODE + SmsConstants.SMS_EXCEPTION_STEP_CODE + "03", "短信发送请求参数为空,参数为:{}"), + + /** + * 腾讯云短信发送异常 + */ + TENCENT_SMS_PARAM_NULL(RuleConstants.THIRD_ERROR_TYPE_CODE + SmsConstants.SMS_EXCEPTION_STEP_CODE + "04", "腾讯云短信发送异常,错误码:{},错误信息:{}"), + + /** + * 短信验证失败,库中找不到短信发送的记录 + */ + SMS_VALIDATE_ERROR_NOT_EXISTED_RECORD(RuleConstants.BUSINESS_ERROR_TYPE_CODE + SmsConstants.SMS_EXCEPTION_STEP_CODE + "05", "验证失败,库中没有该短信发送记录"), + + /** + * 短信验证失败,验证码失效,可能这个短信验证码用过了 + */ + SMS_VALIDATE_ERROR_INVALIDATE_STATUS(RuleConstants.BUSINESS_ERROR_TYPE_CODE + SmsConstants.SMS_EXCEPTION_STEP_CODE + "06", "验证失败,短信验证码失效,请检查是否被使用过"), + + /** + * 短信验证失败,验证码错误 + */ + SMS_VALIDATE_ERROR_INVALIDATE_CODE(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SmsConstants.SMS_EXCEPTION_STEP_CODE + "07", "验证失败,短信验证码错误"), + + /** + * 短信验证失败,验证码超时 + */ + SMS_VALIDATE_ERROR_INVALIDATE_TIME(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SmsConstants.SMS_EXCEPTION_STEP_CODE + "08", "验证失败,验证码超时"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + SmsExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/expander/SmsConfigExpander.java b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/expander/SmsConfigExpander.java new file mode 100644 index 000000000..0c3916958 --- /dev/null +++ b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/expander/SmsConfigExpander.java @@ -0,0 +1,55 @@ +package cn.stylefeng.roses.kernel.sms.api.expander; + +import cn.stylefeng.roses.kernel.config.api.context.ConfigContext; + +/** + * 短信相关的配置拓展 + * + * @author fengshuonan + * @date 2020/10/26 22:09 + */ +public class SmsConfigExpander { + + /** + * 获取短信验证码失效时间(单位:秒) + *

+ * 默认300秒 + * + * @author fengshuonan + * @date 2020/10/26 22:09 + */ + public static Integer getSmsValidateExpiredSeconds() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_SMS_VALIDATE_EXPIRED_SECONDS", Integer.class, 300); + } + + /** + * 阿里云短信的accessKeyId + * + * @author fengshuonan + * @date 2020/12/1 21:20 + */ + public static String getAliyunSmsAccessKeyId() { + return ConfigContext.me().getConfigValue("SYS_ALIYUN_SMS_ACCESS_KEY_ID", String.class); + } + + /** + * 阿里云短信的accessKeySecret + * + * @author fengshuonan + * @date 2020/12/1 21:20 + */ + public static String getAliyunSmsAccessKeySecret() { + return ConfigContext.me().getConfigValue("SYS_ALIYUN_SMS_ACCESS_KEY_SECRET", String.class); + } + + /** + * 阿里云短信的签名 + * + * @author fengshuonan + * @date 2020/12/1 21:20 + */ + public static String getAliyunSmsSignName() { + return ConfigContext.me().getConfigValue("SYS_ALIYUN_SMS_SIGN_NAME", String.class); + } + +} diff --git a/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/pojo/AliyunSmsProperties.java b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/pojo/AliyunSmsProperties.java new file mode 100644 index 000000000..b04327657 --- /dev/null +++ b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/pojo/AliyunSmsProperties.java @@ -0,0 +1,49 @@ +package cn.stylefeng.roses.kernel.sms.api.pojo; + +import lombok.Data; + +/** + * 阿里云oss相关配置 + * + * @author fengshuonan + * @date 2018-06-27-下午1:20 + */ +@Data +public class AliyunSmsProperties { + + /** + * accessKeyId + */ + private String accessKeyId; + + /** + * accessKeySecret + */ + private String accessKeySecret; + + /** + * 签名名称 + */ + private String signName; + + /** + * 地域id(阿里云sdk默认的,一般不用修改) + */ + private String regionId = "cn-hangzhou"; + + /** + * domain(阿里云sdk默认的,一般不用修改) + */ + private String smsDomain = "dysmsapi.aliyuncs.com"; + + /** + * version(阿里云sdk默认的,一般不用修改) + */ + private String smsVersion = "2017-05-25"; + + /** + * sms发送(阿里云sdk默认的,一般不用修改) + */ + private String smsSendAction = "SendSms"; + +} diff --git a/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/pojo/TencentSmsProperties.java b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/pojo/TencentSmsProperties.java new file mode 100644 index 000000000..e16b883f4 --- /dev/null +++ b/kernel-d-sms/sms-api/src/main/java/cn/stylefeng/roses/kernel/sms/api/pojo/TencentSmsProperties.java @@ -0,0 +1,40 @@ +package cn.stylefeng.roses.kernel.sms.api.pojo; + +import lombok.Data; + +/** + * 腾讯云短信配置 + * + * @author fengshuonan + * @date 2020/5/24 18:01 + */ +@Data +public class TencentSmsProperties { + + /** + * secretId + */ + private String secretId; + + /** + * secretKey + */ + private String secretKey; + + /** + * 应用控制台应用管理中的应用id + *

+ * 在 [短信控制台] 添加应用后生成的实际 SDKAppID,例如1400006666 + *

+ * 短信控制台:https://console.cloud.tencent.com/smsv2 + */ + private String sdkAppId; + + /** + * 签名,一般为中文名 + *

+ * 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名,可登录 [短信控制台] 查看签名信息 + */ + private String sign; + +} diff --git a/kernel-d-sms/sms-business-validation/README.md b/kernel-d-sms/sms-business-validation/README.md new file mode 100644 index 000000000..bbf733f21 --- /dev/null +++ b/kernel-d-sms/sms-business-validation/README.md @@ -0,0 +1 @@ +带短信校验功能的业务模块 \ No newline at end of file diff --git a/kernel-d-sms/sms-business-validation/pom.xml b/kernel-d-sms/sms-business-validation/pom.xml new file mode 100644 index 000000000..ee219a60e --- /dev/null +++ b/kernel-d-sms/sms-business-validation/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-sms + 1.0.0 + ../pom.xml + + + sms-business-validation + + jar + + + + + + cn.stylefeng.roses + sms-api + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/controller/SmsSenderController.java b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/controller/SmsSenderController.java new file mode 100644 index 000000000..093578fad --- /dev/null +++ b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/controller/SmsSenderController.java @@ -0,0 +1,77 @@ +package cn.stylefeng.roses.kernel.sms.modular.controller; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.RandomUtil; +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import cn.stylefeng.roses.kernel.sms.modular.param.SysSmsInfoParam; +import cn.stylefeng.roses.kernel.sms.modular.service.SysSmsInfoService; +import cn.stylefeng.roses.kernel.sms.modular.param.SysSmsSendParam; +import cn.stylefeng.roses.kernel.sms.modular.param.SysSmsVerifyParam; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.HashMap; + +/** + * 短信发送控制器 + * + * @author fengshuonan + * @date 2020/10/26 18:34 + */ +@RestController +@ApiResource(name = "短信发送控制器") +public class SmsSenderController { + + @Resource + private SysSmsInfoService sysSmsInfoService; + + /** + * 发送记录查询 + * + * @author fengshuonan + * @date 2020/10/26 18:34 + */ + @GetResource(name = "发送记录查询", path = "/sms/page") + public ResponseData page(SysSmsInfoParam sysSmsInfoParam) { + return new SuccessResponseData(sysSmsInfoService.page(sysSmsInfoParam)); + } + + /** + * 发送验证码短信 + * + * @author fengshuonan + * @date 2020/10/26 18:34 + */ + @PostResource(name = "发送验证码短信", path = "/sms/sendLoginMessage") + public ResponseData sendLoginMessage(@RequestBody @Validated SysSmsSendParam sysSmsSendParam) { + + // 清空params参数 + sysSmsSendParam.setParams(null); + + // 设置模板中的参数 + HashMap paramMap = CollectionUtil.newHashMap(); + paramMap.put("code", RandomUtil.randomNumbers(6)); + sysSmsSendParam.setParams(paramMap); + + return new SuccessResponseData(sysSmsInfoService.sendShortMessage(sysSmsSendParam)); + } + + /** + * 验证短信验证码 + * + * @author fengshuonan + * @date 2020/10/26 18:35 + */ + @PostResource(name = "验证短信验证码", path = "/sms/validateMessage") + public ResponseData validateMessage(@RequestBody @Validated SysSmsVerifyParam sysSmsVerifyParam) { + sysSmsInfoService.validateSmsInfo(sysSmsVerifyParam); + return new SuccessResponseData("短信验证成功"); + } + +} diff --git a/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/entity/SysSms.java b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/entity/SysSms.java new file mode 100644 index 000000000..d75ab844c --- /dev/null +++ b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/entity/SysSms.java @@ -0,0 +1,75 @@ +package cn.stylefeng.roses.kernel.sms.modular.entity; + +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.fasterxml.jackson.annotation.JsonFormat; +import cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +/** + * 系统短信表 + * + * @author fengshuonan + * @date 2020/10/26 21:29 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_sms") +public class SysSms extends BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + /** + * 手机号 + */ + @TableField("phone_numbers") + private String phoneNumbers; + + /** + * 短信验证码 + */ + @TableField("validate_code") + private String validateCode; + + /** + * 短信模板ID + */ + @TableField("template_code") + private String templateCode; + + /** + * 回执id,可根据该id查询具体的发送状态 + */ + @TableField("biz_id") + private String bizId; + + /** + * 发送状态(字典 0 未发送,1 发送成功,2 发送失败,3 失效) + */ + @TableField("status") + private Integer status; + + /** + * 来源(字典 1 app, 2 pc, 3 其他) + */ + @TableField("source") + private Integer source; + + /** + * 失效时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @TableField("invalid_time") + private Date invalidTime; +} diff --git a/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/enums/SmsSendSourceEnum.java b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/enums/SmsSendSourceEnum.java new file mode 100644 index 000000000..85d27672b --- /dev/null +++ b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/enums/SmsSendSourceEnum.java @@ -0,0 +1,34 @@ +package cn.stylefeng.roses.kernel.sms.modular.enums; + +import lombok.Getter; + +/** + * 短信发送业务枚举 + * + * @author fengshuonan + * @date 2020/10/26 21:29 + */ +@Getter +public enum SmsSendSourceEnum { + + /** + * APP + */ + APP(1), + + /** + * PC + */ + PC(2), + + /** + * OTHER + */ + OTHER(3); + + private final Integer code; + + SmsSendSourceEnum(Integer code) { + this.code = code; + } +} diff --git a/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/enums/SmsSendStatusEnum.java b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/enums/SmsSendStatusEnum.java new file mode 100644 index 000000000..d7e7ba5f7 --- /dev/null +++ b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/enums/SmsSendStatusEnum.java @@ -0,0 +1,66 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.sms.modular.enums; + +import lombok.Getter; + +/** + * 短信发送状态枚举 + * + * @author fengshuonan + * @date 2020/10/26 21:29 + */ +@Getter +public enum SmsSendStatusEnum { + + /** + * 未发送 + */ + WAITING(0, "未发送"), + + /** + * 发送成功 + */ + SUCCESS(1, "发送成功"), + + /** + * 发送失败 + */ + FAILED(2, "发送失败"), + + /** + * 失效 + */ + INVALID(3, "失效"); + + private final Integer code; + + private final String message; + + SmsSendStatusEnum(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/enums/SmsTypeEnum.java b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/enums/SmsTypeEnum.java new file mode 100644 index 000000000..6ace74720 --- /dev/null +++ b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/enums/SmsTypeEnum.java @@ -0,0 +1,56 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.sms.modular.enums; + +import lombok.Getter; + +/** + * 短信类型枚举 + * + * @author fengshuonan + * @date 2020/10/26 21:30 + */ +@Getter +public enum SmsTypeEnum { + + /** + * 验证类短信 + */ + SMS(1, "验证类短信"), + + /** + * 纯发送短信 + */ + MESSAGE(2, "纯发送短信"); + + private final Integer code; + + private final String message; + + SmsTypeEnum(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/mapper/SysSmsMapper.java b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/mapper/SysSmsMapper.java new file mode 100644 index 000000000..93eb04bb8 --- /dev/null +++ b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/mapper/SysSmsMapper.java @@ -0,0 +1,14 @@ +package cn.stylefeng.roses.kernel.sms.modular.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.stylefeng.roses.kernel.sms.modular.entity.SysSms; + +/** + * 系统短信mapper接口 + * + * @author fengshuonan + * @date 2020/10/26 21:30 + */ +public interface SysSmsMapper extends BaseMapper { + +} diff --git a/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/mapper/mapping/SysSmsMapper.xml b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/mapper/mapping/SysSmsMapper.xml new file mode 100644 index 000000000..02ca87867 --- /dev/null +++ b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/mapper/mapping/SysSmsMapper.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/param/SysSmsInfoParam.java b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/param/SysSmsInfoParam.java new file mode 100644 index 000000000..3f1c174ff --- /dev/null +++ b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/param/SysSmsInfoParam.java @@ -0,0 +1,58 @@ +package cn.stylefeng.roses.kernel.sms.modular.param; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + * 系统短信表 + * + * @author fengshuonan + * @date 2020/10/26 22:16 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysSmsInfoParam extends BaseRequest { + + /** + * 主键 + */ + private Long id; + + /** + * 手机号 + */ + private String phoneNumbers; + + /** + * 短信验证码 + */ + private String validateCode; + + /** + * 短信模板ID + */ + private String templateCode; + + /** + * 回执id,可根据该id查询具体的发送状态 + */ + private String bizId; + + /** + * 发送状态(字典 0 未发送,1 发送成功,2 发送失败,3 失效) + */ + private Integer status; + + /** + * 来源(字典 1 app, 2 pc, 3 其他) + */ + private Integer source; + + /** + * 失效时间 + */ + private Date invalidTime; +} diff --git a/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/param/SysSmsSendParam.java b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/param/SysSmsSendParam.java new file mode 100644 index 000000000..672615dbb --- /dev/null +++ b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/param/SysSmsSendParam.java @@ -0,0 +1,46 @@ +package cn.stylefeng.roses.kernel.sms.modular.param; + +import cn.stylefeng.roses.kernel.sms.modular.enums.SmsSendSourceEnum; +import cn.stylefeng.roses.kernel.sms.modular.enums.SmsTypeEnum; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.Map; + +/** + * 发送短信的参数 + * + * @author fengshuonan + * @date 2020/10/26 22:16 + */ +@Data +public class SysSmsSendParam { + + /** + * 手机号 + */ + @NotBlank(message = "手机号码为空,请检查phoneNumbers参数") + private String phoneNumbers; + + /** + * 模板号 + */ + @NotBlank(message = "模板号为空,请检查templateCode参数") + private String templateCode; + + /** + * 模板中的参数 + */ + private Map params; + + /** + * 发送源 + */ + private SmsSendSourceEnum smsSendSourceEnum = SmsSendSourceEnum.PC; + + /** + * 消息类型,1验证码,2消息,默认不传为验证码 + */ + private SmsTypeEnum smsTypeEnum = SmsTypeEnum.SMS; + +} diff --git a/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/param/SysSmsVerifyParam.java b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/param/SysSmsVerifyParam.java new file mode 100644 index 000000000..21809f3b6 --- /dev/null +++ b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/param/SysSmsVerifyParam.java @@ -0,0 +1,40 @@ +package cn.stylefeng.roses.kernel.sms.modular.param; + +import cn.stylefeng.roses.kernel.sms.modular.enums.SmsSendSourceEnum; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 验证短信的参数 + * + * @author fengshuonan + * @date 2020/10/26 21:32 + */ +@Data +public class SysSmsVerifyParam { + + /** + * 手机号 + */ + @NotBlank(message = "手机号不能为空") + private String phoneNumbers; + + /** + * 验证码 + */ + @NotBlank(message = "验证码不能为空") + private String code; + + /** + * 模板号 + */ + @NotBlank(message = "模板号不能为空") + private String templateCode; + + /** + * 来源 + */ + private SmsSendSourceEnum smsSendSourceEnum = SmsSendSourceEnum.PC; + +} diff --git a/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/service/SysSmsInfoService.java b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/service/SysSmsInfoService.java new file mode 100644 index 000000000..3b3f96360 --- /dev/null +++ b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/service/SysSmsInfoService.java @@ -0,0 +1,71 @@ +package cn.stylefeng.roses.kernel.sms.modular.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.sms.modular.entity.SysSms; +import cn.stylefeng.roses.kernel.sms.modular.enums.SmsSendStatusEnum; +import cn.stylefeng.roses.kernel.sms.modular.param.SysSmsInfoParam; +import cn.stylefeng.roses.kernel.sms.modular.param.SysSmsSendParam; +import cn.stylefeng.roses.kernel.sms.modular.param.SysSmsVerifyParam; + +/** + * 系统短信service接口 + * + * @author fengshuonan + * @date 2020/10/26 22:16 + */ +public interface SysSmsInfoService extends IService { + + /** + * 发送短信 + * + * @param sysSmsSendParam 短信发送参数 + * @return true-成功,false-失败 + * @author fengshuonan + * @date 2020/10/26 22:16 + */ + boolean sendShortMessage(SysSmsSendParam sysSmsSendParam); + + /** + * 存储短信验证信息 + * + * @param sysSmsSendParam 发送参数 + * @param validateCode 验证码 + * @return 短信记录id + * @author fengshuonan + * @date 2020/10/26 22:16 + */ + Long saveSmsInfo(SysSmsSendParam sysSmsSendParam, String validateCode); + + /** + * 更新短息发送状态 + * + * @param smsId 短信记录id + * @param smsSendStatusEnum 发送状态枚举 + * @author fengshuonan + * @date 2020/10/26 22:16 + */ + void updateSmsInfo(Long smsId, SmsSendStatusEnum smsSendStatusEnum); + + /** + * 校验验证码是否正确 + *

+ * 如果校验失败,或者短信超时,则会抛出异常 + * + * @param sysSmsVerifyParam 短信校验参数 + * @author fengshuonan + * @date 2020/10/26 22:16 + */ + void validateSmsInfo(SysSmsVerifyParam sysSmsVerifyParam); + + /** + * 短信发送记录查询 + * + * @param sysSmsInfoParam 查询参数 + * @return 查询分页结果 + * @author fengshuonan + * @date 2020/10/26 22:17 + */ + PageResult page(SysSmsInfoParam sysSmsInfoParam); + +} diff --git a/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/service/impl/SysSmsInfoServiceImpl.java b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/service/impl/SysSmsInfoServiceImpl.java new file mode 100644 index 000000000..0f0a41f44 --- /dev/null +++ b/kernel-d-sms/sms-business-validation/src/main/java/cn/stylefeng/roses/kernel/sms/modular/service/impl/SysSmsInfoServiceImpl.java @@ -0,0 +1,195 @@ +package cn.stylefeng.roses.kernel.sms.modular.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.stylefeng.roses.kernel.sms.modular.enums.SmsTypeEnum; +import cn.stylefeng.roses.kernel.sms.modular.mapper.SysSmsMapper; +import cn.stylefeng.roses.kernel.sms.modular.param.SysSmsInfoParam; +import cn.stylefeng.roses.kernel.sms.modular.service.SysSmsInfoService; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.sms.api.SmsSenderApi; +import cn.stylefeng.roses.kernel.sms.api.exception.SmsException; +import cn.stylefeng.roses.kernel.sms.api.expander.SmsConfigExpander; +import cn.stylefeng.roses.kernel.sms.modular.entity.SysSms; +import cn.stylefeng.roses.kernel.sms.modular.enums.SmsSendStatusEnum; +import cn.stylefeng.roses.kernel.sms.modular.param.SysSmsSendParam; +import cn.stylefeng.roses.kernel.sms.modular.param.SysSmsVerifyParam; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import static cn.stylefeng.roses.kernel.sms.api.constants.SmsConstants.SMS_CODE_PARAM_NAME; +import static cn.stylefeng.roses.kernel.sms.api.exception.enums.SmsExceptionEnum.*; + +/** + * 系统短信接口实现类 + * + * @author fengshuonan + * @date 2020/10/26 22:17 + */ +@Service +@Slf4j +public class SysSmsInfoServiceImpl extends ServiceImpl implements SysSmsInfoService { + + @Resource + private SmsSenderApi smsSenderApi; + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean sendShortMessage(SysSmsSendParam sysSmsSendParam) { + + Map params = sysSmsSendParam.getParams(); + + // 1. 如果是纯消息发送,直接发送,校验类短信要把验证码存库 + if (SmsTypeEnum.MESSAGE.equals(sysSmsSendParam.getSmsTypeEnum())) { + smsSenderApi.sendSms(sysSmsSendParam.getPhoneNumbers(), sysSmsSendParam.getTemplateCode(), params); + } + + // 2. 如果参数中有code参数,则获取参数param中的code值 + String validateCode; + if (params != null && params.get(SMS_CODE_PARAM_NAME) != null) { + validateCode = params.get(SMS_CODE_PARAM_NAME).toString(); + } + + // 3. 如果参数中没有code参数,自动装填一个code参数,用于放随机的验证码 + else { + validateCode = RandomUtil.randomNumbers(6); + if (params == null) { + params = CollectionUtil.newHashMap(); + } + params.put(SMS_CODE_PARAM_NAME, validateCode); + } + + // 4. 存储短信到数据库 + Long smsId = this.saveSmsInfo(sysSmsSendParam, validateCode); + + log.info(">>> 开始发送短信:发送的电话号码= " + sysSmsSendParam.getPhoneNumbers() + ",发送的模板号=" + sysSmsSendParam.getTemplateCode() + ",发送的参数是:" + JSON.toJSONString(params)); + + // 5. 发送短信 + smsSenderApi.sendSms(sysSmsSendParam.getPhoneNumbers(), sysSmsSendParam.getTemplateCode(), params); + + // 6. 更新短信发送状态 + this.updateSmsInfo(smsId, SmsSendStatusEnum.SUCCESS); + + return true; + } + + @Override + public Long saveSmsInfo(SysSmsSendParam sysSmsSendParam, String validateCode) { + + // 获取当前时间 + Date nowDate = new Date(); + + // 计算短信失效时间 + Integer invalidedSeconds = SmsConfigExpander.getSmsValidateExpiredSeconds(); + long invalidateTime = nowDate.getTime() + invalidedSeconds * 1000; + Date invalidate = new Date(invalidateTime); + + SysSms sysSms = new SysSms(); + sysSms.setCreateTime(nowDate); + sysSms.setInvalidTime(invalidate); + sysSms.setPhoneNumbers(sysSmsSendParam.getPhoneNumbers()); + sysSms.setStatus(SmsSendStatusEnum.WAITING.getCode()); + sysSms.setSource(sysSmsSendParam.getSmsSendSourceEnum().getCode()); + sysSms.setTemplateCode(sysSmsSendParam.getTemplateCode()); + sysSms.setValidateCode(validateCode); + + this.save(sysSms); + + log.info(">>> 发送短信,存储短信到数据库,数据为:" + JSON.toJSONString(sysSms)); + + return sysSms.getId(); + } + + @Override + public void updateSmsInfo(Long smsId, SmsSendStatusEnum smsSendStatusEnum) { + SysSms sysSms = this.getById(smsId); + sysSms.setStatus(smsSendStatusEnum.getCode()); + this.updateById(sysSms); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void validateSmsInfo(SysSmsVerifyParam sysSmsVerifyParam) { + + // 查询有没有这条记录 + LambdaQueryWrapper smsQueryWrapper = new LambdaQueryWrapper<>(); + smsQueryWrapper.eq(SysSms::getPhoneNumbers, sysSmsVerifyParam.getPhoneNumbers()) + .and(f -> f.eq(SysSms::getSource, sysSmsVerifyParam.getSmsSendSourceEnum().getCode())) + .and(f -> f.eq(SysSms::getTemplateCode, sysSmsVerifyParam.getTemplateCode())); + smsQueryWrapper.orderByDesc(SysSms::getCreateTime); + + List sysSmsList = this.list(smsQueryWrapper); + + log.info(">>> 验证短信Provider接口,查询到sms记录:" + JSON.toJSONString(sysSmsList)); + + // 如果找不到记录,提示验证失败 + if (ObjectUtil.isEmpty(sysSmsList)) { + throw new SmsException(SMS_VALIDATE_ERROR_NOT_EXISTED_RECORD); + } + + // 获取最近发送的第一条 + SysSms sysSms = sysSmsList.get(0); + + // 先判断状态是不是失效的状态 + if (SmsSendStatusEnum.INVALID.getCode().equals(sysSms.getStatus())) { + throw new SmsException(SMS_VALIDATE_ERROR_INVALIDATE_STATUS); + } + + // 如果验证码和传过来的不一致 + if (!sysSmsVerifyParam.getCode().equals(sysSms.getValidateCode())) { + throw new SmsException(SMS_VALIDATE_ERROR_INVALIDATE_CODE); + } + + // 判断是否超时 + Date invalidTime = sysSms.getInvalidTime(); + if (ObjectUtil.isEmpty(invalidTime) || new Date().after(invalidTime)) { + throw new SmsException(SMS_VALIDATE_ERROR_INVALIDATE_TIME); + } + + // 验证成功把短信设置成失效 + sysSms.setStatus(SmsSendStatusEnum.INVALID.getCode()); + this.updateById(sysSms); + } + + @Override + public PageResult page(SysSmsInfoParam sysSmsInfoParam) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + if (ObjectUtil.isNotNull(sysSmsInfoParam)) { + + // 根据手机号模糊查询 + if (ObjectUtil.isNotEmpty(sysSmsInfoParam.getPhoneNumbers())) { + queryWrapper.like(SysSms::getPhoneNumbers, sysSmsInfoParam.getPhoneNumbers()); + } + + // 根据发送状态查询(字典 0 未发送,1 发送成功,2 发送失败,3 失效) + if (ObjectUtil.isNotEmpty(sysSmsInfoParam.getStatus())) { + queryWrapper.eq(SysSms::getStatus, sysSmsInfoParam.getStatus()); + } + + // 根据来源查询(字典 1 app, 2 pc, 3 其他) + if (ObjectUtil.isNotEmpty(sysSmsInfoParam.getSource())) { + queryWrapper.eq(SysSms::getSource, sysSmsInfoParam.getSource()); + } + } + + // 查询分页结果 + Page page = this.page(PageFactory.defaultPage(), queryWrapper); + return PageResultFactory.createPageResult(page); + } + + +} diff --git a/kernel-d-sms/sms-sdk-aliyun/README.md b/kernel-d-sms/sms-sdk-aliyun/README.md new file mode 100644 index 000000000..6821bce92 --- /dev/null +++ b/kernel-d-sms/sms-sdk-aliyun/README.md @@ -0,0 +1 @@ +短信模块,基于阿里云实现 \ No newline at end of file diff --git a/kernel-d-sms/sms-sdk-aliyun/pom.xml b/kernel-d-sms/sms-sdk-aliyun/pom.xml new file mode 100644 index 000000000..0457a5061 --- /dev/null +++ b/kernel-d-sms/sms-sdk-aliyun/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-sms + 1.0.0 + ../pom.xml + + + sms-sdk-aliyun + + jar + + + + + + cn.stylefeng.roses + sms-api + 1.0.0 + + + + + com.aliyun + aliyun-java-sdk-core + + + com.aliyun + aliyun-java-sdk-ecs + + + + + diff --git a/kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/AliyunSmsSender.java b/kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/AliyunSmsSender.java new file mode 100644 index 000000000..63927d71a --- /dev/null +++ b/kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/AliyunSmsSender.java @@ -0,0 +1,183 @@ +package cn.stylefeng.roses.kernel.aliyun; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.aliyuncs.CommonRequest; +import com.aliyuncs.CommonResponse; +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.profile.DefaultProfile; +import cn.stylefeng.roses.kernel.aliyun.enums.AliyunSmsResultEnum; +import cn.stylefeng.roses.kernel.aliyun.msign.MultiSignManager; +import cn.stylefeng.roses.kernel.sms.api.SmsSenderApi; +import cn.stylefeng.roses.kernel.sms.api.exception.SmsException; +import cn.stylefeng.roses.kernel.sms.api.exception.enums.SmsExceptionEnum; +import cn.stylefeng.roses.kernel.sms.api.pojo.AliyunSmsProperties; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +import static cn.stylefeng.roses.kernel.sms.api.exception.enums.SmsExceptionEnum.SEND_SMS_PARAM_NULL; + +/** + * 阿里云短信发送服务 + * + * @author fengshuonan + * @date 2020/10/26 17:12 + */ +@Slf4j +public class AliyunSmsSender implements SmsSenderApi { + + private final MultiSignManager multiSignManager; + + private final AliyunSmsProperties aliyunSmsProperties; + + public AliyunSmsSender(MultiSignManager multiSignManager, AliyunSmsProperties aliyunSmsProperties) { + this.multiSignManager = multiSignManager; + this.aliyunSmsProperties = aliyunSmsProperties; + } + + @Override + public void sendSms(String phone, String templateCode, Map params) { + + log.info("开始发送阿里云短信,手机号是:" + phone + ",模板号是:" + templateCode + ",参数是:" + params); + + // 检验参数是否合法 + assertSendSmsParams(phone, templateCode, params, aliyunSmsProperties); + + // 初始化client profile + IAcsClient iAcsClient = initClient(); + + // 组装请求对象 + JSONObject smsRes = createSmsRequest(phone, templateCode, params, iAcsClient); + + // 如果返回ok则发送成功 + if (!AliyunSmsResultEnum.OK.getCode().equals(smsRes.getString("Code"))) { + + // 返回其他状态码根据情况抛出业务异常 + String code = AliyunSmsResultEnum.SYSTEM_ERROR.getCode(); + String errorMessage = AliyunSmsResultEnum.SYSTEM_ERROR.getMessage(); + for (AliyunSmsResultEnum smsExceptionEnum : AliyunSmsResultEnum.values()) { + if (smsExceptionEnum.getCode().equals(smsRes.getString("Code"))) { + code = smsExceptionEnum.getCode(); + errorMessage = smsExceptionEnum.getMessage(); + } + } + + // 组装错误信息 + String userTip = SmsExceptionEnum.ALIYUN_SMS_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, code, errorMessage); + throw new SmsException(SmsExceptionEnum.ALIYUN_SMS_ERROR.getErrorCode(), finalUserTip); + } + } + + /** + * 初始化短信发送的客户端 + * + * @author fengshuonan + * @date 2018/7/6 下午3:57 + */ + private IAcsClient initClient() { + final String accessKeyId = aliyunSmsProperties.getAccessKeyId(); + final String accessKeySecret = aliyunSmsProperties.getAccessKeySecret(); + + // 创建DefaultAcsClient实例并初始化 + DefaultProfile profile = DefaultProfile.getProfile(aliyunSmsProperties.getRegionId(), accessKeyId, accessKeySecret); + return new DefaultAcsClient(profile); + } + + /** + * 组装请求对象 + * + * @author fengshuonan + * @date 2018/7/6 下午3:00 + */ + private JSONObject createSmsRequest(String phoneNumber, String templateCode, Map params, IAcsClient acsClient) { + CommonRequest request = new CommonRequest(); + request.setSysDomain(aliyunSmsProperties.getSmsDomain()); + request.setSysVersion(aliyunSmsProperties.getSmsVersion()); + request.setSysAction(aliyunSmsProperties.getSmsSendAction()); + + // 接收短信的手机号码 + request.putQueryParameter("PhoneNumbers", phoneNumber); + + // 短信签名名称。请在控制台签名管理页面签名名称一列查看(必须是已添加、并通过审核的短信签名)。 + request.putQueryParameter("SignName", this.getSmsSign(phoneNumber)); + + // 短信模板ID + request.putQueryParameter("TemplateCode", templateCode); + + // 短信模板变量对应的实际值,JSON格式。 + request.putQueryParameter("TemplateParam", JSON.toJSONString(params)); + + //请求失败这里会抛ClientException异常 + CommonResponse commonResponse; + try { + commonResponse = acsClient.getCommonResponse(request); + String data = commonResponse.getData(); + String jsonResult = data.replaceAll("'\'", ""); + log.info("获取到发送短信的响应结果!{}", jsonResult); + return JSON.parseObject(jsonResult); + } catch (ClientException e) { + log.error("初始化阿里云sms异常!可能是accessKey和secret错误!", e); + + // 组装错误信息 + String userTip = SmsExceptionEnum.ALIYUN_SMS_KEY_ERROR.getUserTip(); + String finalUserTip = StrUtil.format(userTip, aliyunSmsProperties.getAccessKeyId()); + throw new SmsException(SmsExceptionEnum.ALIYUN_SMS_ERROR.getErrorCode(), finalUserTip); + } + } + + /** + * 校验发送短信的参数是否正确 + * + * @author fengshuonan + * @date 2018/7/6 下午3:19 + */ + private void assertSendSmsParams(String phoneNumber, String templateCode, Map params, + AliyunSmsProperties aliyunSmsProperties) { + + if (StrUtil.isBlank(phoneNumber)) { + String userTip = StrUtil.format(SEND_SMS_PARAM_NULL.getUserTip(), "电话号码"); + throw new SmsException(SEND_SMS_PARAM_NULL.getErrorCode(), userTip); + } + + if (StrUtil.isBlank(templateCode)) { + String userTip = StrUtil.format(SEND_SMS_PARAM_NULL.getUserTip(), "模板号templateCode"); + throw new SmsException(SEND_SMS_PARAM_NULL.getErrorCode(), userTip); + } + + if (ObjectUtil.isEmpty(params)) { + String userTip = StrUtil.format(SEND_SMS_PARAM_NULL.getUserTip(), "模板参数"); + throw new SmsException(SEND_SMS_PARAM_NULL.getErrorCode(), userTip); + } + + if (ObjectUtil.isEmpty(aliyunSmsProperties)) { + String userTip = StrUtil.format(SEND_SMS_PARAM_NULL.getUserTip(), "短信配置properties"); + throw new SmsException(SEND_SMS_PARAM_NULL.getErrorCode(), userTip); + } + + } + + /** + * 获取sms发送的sign标识,参数phone是发送的手机号码 + * + * @author stylefeng + * @date 2018/8/13 21:23 + */ + private String getSmsSign(String phone) { + String signName = aliyunSmsProperties.getSignName(); + + // 如果是单个签名就用一个签名发 + if (!signName.contains(",")) { + log.info("发送短信,签名为:" + signName + ",电话为:" + phone); + return signName; + } else { + return multiSignManager.getSign(phone, signName); + } + } + +} diff --git a/kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/enums/AliyunSmsResultEnum.java b/kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/enums/AliyunSmsResultEnum.java new file mode 100644 index 000000000..8948b3716 --- /dev/null +++ b/kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/enums/AliyunSmsResultEnum.java @@ -0,0 +1,124 @@ +package cn.stylefeng.roses.kernel.aliyun.enums; + + +import lombok.Getter; + +/** + * 短信发送异常相关枚举 + * + * @author stylefeng + * @date 2018/1/4 22:40 + */ +@Getter +public enum AliyunSmsResultEnum { + + + /** + * 初始化sms客户端错误,accessKey错误 + */ + INIT_SMS_CLIENT_ERROR("SMS_CLIENT_INIT_ERROR", "初始化sms客户端错误,accessKey错误"), + + /** + * 请求参数为空 + */ + PARAM_NULL("NULL", "请求参数为空"), + + /** + * 请求成功 + */ + OK("OK", "请求成功"), + + /** + * RAM权限DENY + */ + RAM_PERMISSION_DENY("isp.RAM_PERMISSION_DENY", "RAM权限DENY"), + + /** + * 产品未开通 + */ + PRODUCT_UNSUBSCRIBE("isv.PRODUCT_UNSUBSCRIBE", "产品未开通"), + + /** + * 账户不存在 + */ + ACCOUNT_NOT_EXISTS("isv.ACCOUNT_NOT_EXISTS", "账户不存在"), + + /** + * 账户异常 + */ + ACCOUNT_ABNORMAL("isv.ACCOUNT_ABNORMAL", "账户异常"), + + /** + * 短信模板不合法 + */ + SMS_TEMPLATE_ILLEGAL("isv.SMS_TEMPLATE_ILLEGAL", "短信模板不合法"), + + /** + * 短信签名不合法 + */ + SMS_SIGNATURE_ILLEGAL("isv.SMS_SIGNATURE_ILLEGAL", "短信签名不合法"), + + /** + * 参数异常 + */ + INVALID_PARAMETERS("isv.INVALID_PARAMETERS", "参数异常"), + + /** + * 系统错误 + */ + SYSTEM_ERROR("isp.SYSTEM_ERROR", "系统错误"), + + /** + * 非法手机号 + */ + MOBILE_NUMBER_ILLEGAL("isv.MOBILE_NUMBER_ILLEGAL", "非法手机号"), + + /** + * 手机号码数量超过限制 + */ + MOBILE_COUNT_OVER_LIMIT("isv.MOBILE_COUNT_OVER_LIMIT", "手机号码数量超过限制"), + + /** + * 模板缺少变量 + */ + TEMPLATE_MISSING_PARAMETERS("isv.TEMPLATE_MISSING_PARAMETERS", "模板缺少变量"), + + /** + * 发送短信过于频繁,请稍后再试 + */ + BUSINESS_LIMIT_CONTROL("isv.BUSINESS_LIMIT_CONTROL", "发送短信过于频繁,请稍后再试"), + + /** + * JSON参数不合法,只接受字符串值 + */ + INVALID_JSON_PARAM("isv.INVALID_JSON_PARAM", "JSON参数不合法,只接受字符串值"), + + /** + * 黑名单管控 + */ + BLACK_KEY_CONTROL_LIMIT("isv.BLACK_KEY_CONTROL_LIMIT", "黑名单管控"), + + /** + * 参数超出长度限制 + */ + PARAM_LENGTH_LIMIT("isv.PARAM_LENGTH_LIMIT", "参数超出长度限制"), + + /** + * 不支持URL + */ + PARAM_NOT_SUPPORT_URL("isv.PARAM_NOT_SUPPORT_URL", "不支持URL"), + + /** + * 账户余额不足 + */ + AMOUNT_NOT_ENOUGH("isv.AMOUNT_NOT_ENOUGH", "账户余额不足"); + + private String code; + + private final String message; + + AliyunSmsResultEnum(String code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/msign/MultiSignManager.java b/kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/msign/MultiSignManager.java new file mode 100644 index 000000000..a24750cc0 --- /dev/null +++ b/kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/msign/MultiSignManager.java @@ -0,0 +1,22 @@ +package cn.stylefeng.roses.kernel.aliyun.msign; + +/** + * 多个签名的缓存管理,为了打破一个签名发送次数的限制 + * + * @author fengshuonan + * @date 2018-09-21-上午10:47 + */ +public interface MultiSignManager { + + /** + * 获取签名 + * + * @param phone 电话 + * @param signName 发送短信用的签名,是一个以逗号隔开的字符串 + * @return 签名 + * @author fengshuonan + * @date 2018/9/21 上午10:51 + */ + String getSign(String phone, String signName); + +} diff --git a/kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/msign/impl/MapBasedMultiSignManager.java b/kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/msign/impl/MapBasedMultiSignManager.java new file mode 100644 index 000000000..627a00ec9 --- /dev/null +++ b/kernel-d-sms/sms-sdk-aliyun/src/main/java/cn/stylefeng/roses/kernel/aliyun/msign/impl/MapBasedMultiSignManager.java @@ -0,0 +1,64 @@ +package cn.stylefeng.roses.kernel.aliyun.msign.impl; + +import cn.hutool.log.Log; +import cn.stylefeng.roses.kernel.aliyun.msign.MultiSignManager; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 获取缓存的map中的签名 + * + * @author fengshuonan + * @date 2018-09-21-上午10:49 + */ +public class MapBasedMultiSignManager implements MultiSignManager { + + private static final Log log = Log.get(); + + private static final int CLEAR_COUNT = 1000; + + private final Map cacheMap = new ConcurrentHashMap<>(); + + @Override + public String getSign(String phone, String signName) { + + //先清除map + clearMap(); + + //分割签名数组 + String[] signNames = signName.split(","); + + //获取上次发送的时候用的哪个签名,这次换一个 + Object lastSignName = cacheMap.get(phone); + if (lastSignName == null) { + cacheMap.put(phone, signNames[0]); + log.info("发送短信,签名为:" + signNames[0] + ",电话为:" + phone); + return signNames[0]; + } else { + for (String name : signNames) { + if (!name.equals(lastSignName)) { + cacheMap.put(phone, name); + log.info("发送短信,签名为:" + name + ",电话为:" + phone); + return name; + } + } + cacheMap.put(phone, signNames[0]); + log.info("发送短信,签名为:" + signNames[0] + ",电话为:" + phone); + return signNames[0]; + } + } + + /** + * 每隔一段时间清除下map + * + * @author fengshuonan + * @date 2018/9/21 上午10:57 + */ + private void clearMap() { + if (cacheMap.size() >= CLEAR_COUNT) { + cacheMap.clear(); + } + } + +} diff --git a/kernel-d-sms/sms-sdk-tencent/README.md b/kernel-d-sms/sms-sdk-tencent/README.md new file mode 100644 index 000000000..24a38b033 --- /dev/null +++ b/kernel-d-sms/sms-sdk-tencent/README.md @@ -0,0 +1 @@ +短信模块,基于腾讯云实现 \ No newline at end of file diff --git a/kernel-d-sms/sms-sdk-tencent/pom.xml b/kernel-d-sms/sms-sdk-tencent/pom.xml new file mode 100644 index 000000000..6121812a1 --- /dev/null +++ b/kernel-d-sms/sms-sdk-tencent/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-sms + 1.0.0 + ../pom.xml + + + sms-sdk-tencent + + jar + + + + + + cn.stylefeng.roses + sms-api + 1.0.0 + + + + + com.tencentcloudapi + tencentcloud-sdk-java + + + + + diff --git a/kernel-d-sms/sms-sdk-tencent/src/main/java/cn/stylefeng/roses/kernel/sms/tencent/TencentSmsSender.java b/kernel-d-sms/sms-sdk-tencent/src/main/java/cn/stylefeng/roses/kernel/sms/tencent/TencentSmsSender.java new file mode 100644 index 000000000..22ad025ce --- /dev/null +++ b/kernel-d-sms/sms-sdk-tencent/src/main/java/cn/stylefeng/roses/kernel/sms/tencent/TencentSmsSender.java @@ -0,0 +1,94 @@ +package cn.stylefeng.roses.kernel.sms.tencent; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.sms.api.SmsSenderApi; +import cn.stylefeng.roses.kernel.sms.api.exception.SmsException; +import cn.stylefeng.roses.kernel.sms.api.pojo.TencentSmsProperties; +import com.tencentcloudapi.common.Credential; +import com.tencentcloudapi.common.exception.TencentCloudSDKException; +import com.tencentcloudapi.common.profile.ClientProfile; +import com.tencentcloudapi.common.profile.HttpProfile; +import com.tencentcloudapi.sms.v20190711.SmsClient; +import com.tencentcloudapi.sms.v20190711.models.SendSmsRequest; +import com.tencentcloudapi.sms.v20190711.models.SendSmsResponse; +import com.tencentcloudapi.sms.v20190711.models.SendStatus; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.Map; + +import static cn.stylefeng.roses.kernel.sms.api.exception.enums.SmsExceptionEnum.TENCENT_SMS_PARAM_NULL; + +/** + * 腾讯云短信发送 + * + * @author fengshuonan + * @date 2020/5/24 17:58 + */ +public class TencentSmsSender implements SmsSenderApi { + + private TencentSmsProperties tencentSmsProperties; + + public TencentSmsSender(TencentSmsProperties tencentSmsProperties) { + this.tencentSmsProperties = tencentSmsProperties; + } + + @Override + public void sendSms(String phone, String templateCode, Map params) { + try { + + // 实例化一个认证对象 + Credential cred = new Credential( + tencentSmsProperties.getSecretId(), tencentSmsProperties.getSecretKey()); + + // 实例化一个 http 选项,可选,无特殊需求时可以跳过 + HttpProfile httpProfile = new HttpProfile(); + ClientProfile clientProfile = new ClientProfile(); + clientProfile.setSignMethod("HmacSHA256"); + clientProfile.setHttpProfile(httpProfile); + + // 实例化 SMS 的 client 对象 + SmsClient client = new SmsClient(cred, "ap-guangzhou", clientProfile); + + // 构建请求参数 + SendSmsRequest req = new SendSmsRequest(); + + // 设置应用id + req.setSmsSdkAppid(tencentSmsProperties.getSdkAppId()); + + // 设置签名 + req.setSign(tencentSmsProperties.getSign()); + + // 设置模板id + req.setTemplateID(templateCode); + + // 默认发送一个手机短信 + String[] phoneNumbers = {"+86" + phone}; + req.setPhoneNumberSet(phoneNumbers); + + // 模板参数 + if (params != null && params.size() > 0) { + LinkedList strings = new LinkedList<>(); + Collection values = params.values(); + for (Object value : values) { + strings.add(value.toString()); + } + req.setTemplateParamSet(ArrayUtil.toArray(strings, String.class)); + } + + SendSmsResponse res = client.SendSms(req); + + SendStatus[] sendStatusSet = res.getSendStatusSet(); + if (sendStatusSet != null && sendStatusSet.length > 0) { + if (!sendStatusSet[0].getCode().equals("Ok")) { + String userTip = StrUtil.format(TENCENT_SMS_PARAM_NULL.getUserTip(), sendStatusSet[0].getCode(), sendStatusSet[0].getMessage()); + throw new SmsException(TENCENT_SMS_PARAM_NULL.getErrorCode(), userTip); + } + } + } catch (TencentCloudSDKException e) { + String userTip = StrUtil.format(TENCENT_SMS_PARAM_NULL.getUserTip(), TENCENT_SMS_PARAM_NULL.getErrorCode(), e.getMessage()); + throw new SmsException(TENCENT_SMS_PARAM_NULL.getErrorCode(), userTip); + } + } +} \ No newline at end of file diff --git a/kernel-d-sms/sms-spring-boot-starter/README.md b/kernel-d-sms/sms-spring-boot-starter/README.md new file mode 100644 index 000000000..0ce5ebe95 --- /dev/null +++ b/kernel-d-sms/sms-spring-boot-starter/README.md @@ -0,0 +1 @@ +短信的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-sms/sms-spring-boot-starter/pom.xml b/kernel-d-sms/sms-spring-boot-starter/pom.xml new file mode 100644 index 000000000..73737ade6 --- /dev/null +++ b/kernel-d-sms/sms-spring-boot-starter/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-sms + 1.0.0 + ../pom.xml + + + sms-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + sms-sdk-aliyun + 1.0.0 + + + + + cn.stylefeng.roses + sms-business-validation + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-sms/sms-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/sms/starter/GunsSmsAutoConfiguration.java b/kernel-d-sms/sms-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/sms/starter/GunsSmsAutoConfiguration.java new file mode 100644 index 000000000..ea98a3942 --- /dev/null +++ b/kernel-d-sms/sms-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/sms/starter/GunsSmsAutoConfiguration.java @@ -0,0 +1,41 @@ +package cn.stylefeng.roses.kernel.sms.starter; + +import cn.stylefeng.roses.kernel.aliyun.AliyunSmsSender; +import cn.stylefeng.roses.kernel.aliyun.msign.impl.MapBasedMultiSignManager; +import cn.stylefeng.roses.kernel.sms.api.SmsSenderApi; +import cn.stylefeng.roses.kernel.sms.api.expander.SmsConfigExpander; +import cn.stylefeng.roses.kernel.sms.api.pojo.AliyunSmsProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 短信的自动配置类 + * + * @author fengshuonan + * @date 2020/12/1 21:18 + */ +@Configuration +public class GunsSmsAutoConfiguration { + + /** + * 短信发送器的配置 + * + * @author fengshuonan + * @date 2020/12/1 21:18 + */ + @Bean + @ConditionalOnMissingBean(SmsSenderApi.class) + public SmsSenderApi smsSenderApi() { + + AliyunSmsProperties aliyunSmsProperties = new AliyunSmsProperties(); + + // 配置默认从系统配置表读取 + aliyunSmsProperties.setAccessKeyId(SmsConfigExpander.getAliyunSmsAccessKeyId()); + aliyunSmsProperties.setAccessKeySecret(SmsConfigExpander.getAliyunSmsAccessKeySecret()); + aliyunSmsProperties.setSignName(SmsConfigExpander.getAliyunSmsSignName()); + + return new AliyunSmsSender(new MapBasedMultiSignManager(), aliyunSmsProperties); + } + +} diff --git a/kernel-d-sms/sms-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-sms/sms-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..2eb731389 --- /dev/null +++ b/kernel-d-sms/sms-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.sms.starter.GunsSmsAutoConfiguration \ No newline at end of file diff --git a/kernel-d-timer/README.md b/kernel-d-timer/README.md new file mode 100644 index 000000000..c5eb7b22f --- /dev/null +++ b/kernel-d-timer/README.md @@ -0,0 +1 @@ +定时任务的模块 \ No newline at end of file diff --git a/kernel-d-timer/pom.xml b/kernel-d-timer/pom.xml new file mode 100644 index 000000000..4ac77fa6e --- /dev/null +++ b/kernel-d-timer/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-timer + + pom + + + timer-api + timer-sdk-hutool + timer-business + timer-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-timer/timer-api/README.md b/kernel-d-timer/timer-api/README.md new file mode 100644 index 000000000..c0de63df2 --- /dev/null +++ b/kernel-d-timer/timer-api/README.md @@ -0,0 +1 @@ +定时任务管理模块的api \ No newline at end of file diff --git a/kernel-d-timer/timer-api/pom.xml b/kernel-d-timer/timer-api/pom.xml new file mode 100644 index 000000000..9109c4c6c --- /dev/null +++ b/kernel-d-timer/timer-api/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-timer + 1.0.0 + ../pom.xml + + + timer-api + + jar + + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/TimerAction.java b/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/TimerAction.java new file mode 100644 index 000000000..afa10dad7 --- /dev/null +++ b/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/TimerAction.java @@ -0,0 +1,21 @@ +package cn.stylefeng.roses.kernel.timer.api; + +/** + * 定时器执行内容的一个基类,任务内容都要实现这个接口 + *

+ * Guns中定时器都要实现本接口,并需要把实现类加入到spring容器中 + * + * @author fengshuonan + * @date 2020/10/27 11:53 + */ +public interface TimerAction { + + /** + * 任务执行的具体内容 + * + * @author stylefeng + * @date 2020/6/28 21:29 + */ + void action(); + +} diff --git a/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/TimerExeService.java b/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/TimerExeService.java new file mode 100644 index 000000000..7ab977b51 --- /dev/null +++ b/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/TimerExeService.java @@ -0,0 +1,35 @@ +package cn.stylefeng.roses.kernel.timer.api; + +/** + * 本接口用来,屏蔽定时任务的多样性 + * + * @author fengshuonan + * @date 2020/10/27 13:18 + */ +public interface TimerExeService { + + /** + * 启动一个定时器 + *

+ * 定时任务表达式书写规范:0/2 * * * * * + *

+ * 六位数,分别是:秒 分 小时 日 月 年 + * + * @param taskId 任务id + * @param cron cron表达式 + * @param className 类的全名,必须是TimerAction的子类 + * @author stylefeng + * @date 2020/7/1 13:51 + */ + void startTimer(String taskId, String cron, String className); + + /** + * 停止一个定时器 + * + * @param taskId 定时任务Id + * @author stylefeng + * @date 2020/7/1 14:08 + */ + void stopTimer(String taskId); + +} diff --git a/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/constants/TimerConstants.java b/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/constants/TimerConstants.java new file mode 100644 index 000000000..67e81687c --- /dev/null +++ b/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/constants/TimerConstants.java @@ -0,0 +1,21 @@ +package cn.stylefeng.roses.kernel.timer.api.constants; + +/** + * 定时任务模块的常量 + * + * @author fengshuonan + * @date 2020/10/27 13:46 + */ +public interface TimerConstants { + + /** + * timer模块的名称 + */ + String TIMER_MODULE_NAME = "kernel-d-timer"; + + /** + * 枚举的步进值 + */ + String TIMER_EXCEPTION_STEP_CODE = "11"; + +} diff --git a/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/enums/TimerJobStatusEnum.java b/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/enums/TimerJobStatusEnum.java new file mode 100644 index 000000000..2f025ab6c --- /dev/null +++ b/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/enums/TimerJobStatusEnum.java @@ -0,0 +1,54 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.timer.api.enums; + +import lombok.Getter; + +/** + * 定时任务的状态 + * + * @author stylefeng + * @date 2020/6/30 20:44 + */ +@Getter +public enum TimerJobStatusEnum { + + /** + * 启动状态 + */ + RUNNING(1), + + /** + * 停止状态 + */ + STOP(2); + + private final Integer code; + + TimerJobStatusEnum(int code) { + this.code = code; + } + +} \ No newline at end of file diff --git a/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/exception/TimerException.java b/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/exception/TimerException.java new file mode 100644 index 000000000..4cfae767a --- /dev/null +++ b/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/exception/TimerException.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.timer.api.exception; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; +import cn.stylefeng.roses.kernel.timer.api.constants.TimerConstants; + +/** + * 定时器任务的异常 + * + * @author fengshuonan + * @date 2020/10/15 15:59 + */ +public class TimerException extends ServiceException { + + public TimerException(AbstractExceptionEnum exception) { + super(TimerConstants.TIMER_MODULE_NAME, exception); + } + + public TimerException(AbstractExceptionEnum exception, String userTip) { + super(TimerConstants.TIMER_MODULE_NAME, exception.getErrorCode(), userTip); + } + +} diff --git a/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/exception/enums/TimerExceptionEnum.java b/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/exception/enums/TimerExceptionEnum.java new file mode 100644 index 000000000..77ded9b1e --- /dev/null +++ b/kernel-d-timer/timer-api/src/main/java/cn/stylefeng/roses/kernel/timer/api/exception/enums/TimerExceptionEnum.java @@ -0,0 +1,48 @@ +package cn.stylefeng.roses.kernel.timer.api.exception.enums; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import cn.stylefeng.roses.kernel.timer.api.constants.TimerConstants; +import lombok.Getter; + +/** + * 定时任务的异常枚举 + * + * @author fengshuonan + * @date 2020/10/27 13:55 + */ +@Getter +public enum TimerExceptionEnum implements AbstractExceptionEnum { + + /** + * 定时任务参数为空 + */ + PARAM_HAS_NULL(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + TimerConstants.TIMER_EXCEPTION_STEP_CODE + "01", "定时任务参数为空,具体为空参数为:{}"), + + /** + * 具体定时任务类找不到 + */ + CLASS_NOT_FOUND(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + TimerConstants.TIMER_EXCEPTION_STEP_CODE + "02", "定时任务类获取不到,请检查系统中是否有该类,具体类为:{}"), + + /** + * 获取不到定时任务详情 + */ + JOB_DETAIL_NOT_FOUND(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + TimerConstants.TIMER_EXCEPTION_STEP_CODE + "03", "定时任务详情获取不到,定时任务id为:{}"); + + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + TimerExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-timer/timer-business/README.md b/kernel-d-timer/timer-business/README.md new file mode 100644 index 000000000..f9f06e0f6 --- /dev/null +++ b/kernel-d-timer/timer-business/README.md @@ -0,0 +1 @@ +定时任务,在线管理定时任务的业务集成 \ No newline at end of file diff --git a/kernel-d-timer/timer-business/pom.xml b/kernel-d-timer/timer-business/pom.xml new file mode 100644 index 000000000..fe11dd5ce --- /dev/null +++ b/kernel-d-timer/timer-business/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-timer + 1.0.0 + ../pom.xml + + + timer-business + + jar + + + + + + cn.stylefeng.roses + timer-api + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/controller/SysTimersController.java b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/controller/SysTimersController.java new file mode 100644 index 000000000..89f67471a --- /dev/null +++ b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/controller/SysTimersController.java @@ -0,0 +1,136 @@ +package cn.stylefeng.roses.kernel.timer.modular.controller; + +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import cn.stylefeng.roses.kernel.timer.modular.service.SysTimersService; +import cn.stylefeng.roses.kernel.timer.modular.param.SysTimersParam; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 定时任务控制器 + * + * @author fengshuonan + * @date 2020/10/27 14:30 + */ +@RestController +@ApiResource(name = "定时任务管理") +public class SysTimersController { + + @Resource + private SysTimersService sysTimersService; + + /** + * 分页查询定时任务 + * + * @author stylefeng + * @date 2020/6/30 18:26 + */ + @GetResource(name = "分页查询定时任务", path = "/sysTimers/page") + public ResponseData page(SysTimersParam sysTimersParam) { + return new SuccessResponseData(sysTimersService.page(sysTimersParam)); + } + + /** + * 获取全部定时任务 + * + * @author stylefeng + * @date 2020/6/30 18:26 + */ + @GetResource(name = "获取全部定时任务", path = "/sysTimers/list") + public ResponseData list(SysTimersParam sysTimersParam) { + return new SuccessResponseData(sysTimersService.list(sysTimersParam)); + } + + /** + * 查看详情定时任务 + * + * @author stylefeng + * @date 2020/6/30 18:26 + */ + @GetResource(name = "查看详情定时任务", path = "/sysTimers/detail") + public ResponseData detail(@Validated(SysTimersParam.detail.class) SysTimersParam sysTimersParam) { + return new SuccessResponseData(sysTimersService.detail(sysTimersParam)); + } + + /** + * 添加定时任务 + * + * @author stylefeng + * @date 2020/6/30 18:26 + */ + @PostResource(name = "添加定时任务", path = "/sysTimers/add") + public ResponseData add(@RequestBody @Validated(SysTimersParam.add.class) SysTimersParam sysTimersParam) { + sysTimersService.add(sysTimersParam); + return new SuccessResponseData(); + } + + /** + * 删除定时任务 + * + * @author stylefeng + * @date 2020/6/30 18:26 + */ + @PostResource(name = "删除定时任务", path = "/sysTimers/delete") + public ResponseData delete(@RequestBody @Validated(SysTimersParam.delete.class) SysTimersParam sysTimersParam) { + sysTimersService.delete(sysTimersParam); + return new SuccessResponseData(); + } + + /** + * 编辑定时任务 + * + * @author stylefeng + * @date 2020/6/30 18:26 + */ + @PostResource(name = "编辑定时任务", path = "/sysTimers/edit") + public ResponseData edit(@RequestBody @Validated(SysTimersParam.edit.class) SysTimersParam sysTimersParam) { + sysTimersService.edit(sysTimersParam); + return new SuccessResponseData(); + } + + /** + * 获取系统的所有任务列表 + * + * @author stylefeng + * @date 2020/7/1 14:34 + */ + @PostResource(name = "获取系统的所有任务列表", path = "/sysTimers/getActionClasses") + public ResponseData getActionClasses() { + List actionClasses = sysTimersService.getActionClasses(); + return new SuccessResponseData(actionClasses); + } + + /** + * 启动定时任务 + * + * @author stylefeng + * @date 2020/7/1 14:34 + */ + @PostResource(name = "启动定时任务", path = "/sysTimers/start") + public ResponseData start(@RequestBody @Validated(BaseRequest.groupOne.class) SysTimersParam sysTimersParam) { + sysTimersService.start(sysTimersParam); + return new SuccessResponseData(); + } + + /** + * 停止定时任务 + * + * @author stylefeng + * @date 2020/7/1 14:34 + */ + @PostResource(name = "停止定时任务", path = "/sysTimers/stop") + public ResponseData stop(@RequestBody @Validated(BaseRequest.groupOne.class) SysTimersParam sysTimersParam) { + sysTimersService.stop(sysTimersParam); + return new SuccessResponseData(); + } + +} \ No newline at end of file diff --git a/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/entity/SysTimers.java b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/entity/SysTimers.java new file mode 100644 index 000000000..bb337e662 --- /dev/null +++ b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/entity/SysTimers.java @@ -0,0 +1,58 @@ +package cn.stylefeng.roses.kernel.timer.modular.entity; + +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 cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 定时任务 + * + * @author stylefeng + * @date 2020/6/30 18:26 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_timers") +public class SysTimers extends BaseEntity { + + /** + * 定时器id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 任务名称 + */ + @TableField("timer_name") + private String timerName; + + /** + * 执行任务的class的类名(实现了TimerTaskRunner接口的类的全称) + */ + @TableField("action_class") + private String actionClass; + + /** + * 定时任务表达式 + */ + @TableField("cron") + private String cron; + + /** + * 状态(字典 1运行 2停止) + */ + @TableField("job_status") + private Integer jobStatus; + + /** + * 备注信息 + */ + @TableField("remark") + private String remark; + +} diff --git a/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/mapper/SysTimersMapper.java b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/mapper/SysTimersMapper.java new file mode 100644 index 000000000..392c06a0f --- /dev/null +++ b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/mapper/SysTimersMapper.java @@ -0,0 +1,16 @@ +package cn.stylefeng.roses.kernel.timer.modular.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.stylefeng.roses.kernel.timer.modular.entity.SysTimers; + +/** + *

+ * 定时任务 Mapper 接口 + *

+ * + * @author stylefeng + * @date 2020/6/30 18:26 + */ +public interface SysTimersMapper extends BaseMapper { + +} diff --git a/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/mapper/mapping/SysTimersMapper.xml b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/mapper/mapping/SysTimersMapper.xml new file mode 100644 index 000000000..ffe00a14e --- /dev/null +++ b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/mapper/mapping/SysTimersMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/param/SysTimersParam.java b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/param/SysTimersParam.java new file mode 100644 index 000000000..1483f9a33 --- /dev/null +++ b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/param/SysTimersParam.java @@ -0,0 +1,54 @@ +package cn.stylefeng.roses.kernel.timer.modular.param; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 定时任务 + * + * @author stylefeng + * @date 2020/6/30 18:26 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysTimersParam extends BaseRequest { + + /** + * 定时器id + */ + @NotNull(message = "主键id不能为空,请检查id字段", groups = {edit.class, detail.class, delete.class, groupOne.class}) + private Long id; + + /** + * 任务名称 + */ + @NotBlank(message = "任务名称不能为空,请检查timerName字段", groups = {add.class, edit.class}) + private String timerName; + + /** + * 执行任务的class的类名(实现了TimerTaskRunner接口的类的全称) + */ + @NotBlank(message = "任务的class的类名不能为空,请检查actionClass字段", groups = {add.class, edit.class}) + private String actionClass; + + /** + * 定时任务表达式 + */ + @NotBlank(message = "定时任务表达式不能为空,请检查cron字段", groups = {add.class, edit.class}) + private String cron; + + /** + * 状态(字典 1运行 2停止) + */ + private Integer jobStatus; + + /** + * 备注信息 + */ + private String remark; + +} diff --git a/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/service/SysTimersService.java b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/service/SysTimersService.java new file mode 100644 index 000000000..e129a946c --- /dev/null +++ b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/service/SysTimersService.java @@ -0,0 +1,102 @@ +package cn.stylefeng.roses.kernel.timer.modular.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.timer.modular.entity.SysTimers; +import cn.stylefeng.roses.kernel.timer.modular.param.SysTimersParam; + +import java.util.List; + +/** + * 定时任务 服务类 + * + * @author stylefeng + * @date 2020/6/30 18:26 + */ +public interface SysTimersService extends IService { + + /** + * 分页查询定时任务 + * + * @param sysTimersParam 查询参数 + * @return 查询分页结果 + * @author stylefeng + * @date 2020/6/30 18:26 + */ + PageResult page(SysTimersParam sysTimersParam); + + /** + * 查询所有定时任务 + * + * @param sysTimersParam 查询参数 + * @return 定时任务列表 + * @author stylefeng + * @date 2020/6/30 18:26 + */ + List list(SysTimersParam sysTimersParam); + + /** + * 添加定时任务 + * + * @param sysTimersParam 添加参数 + * @author stylefeng + * @date 2020/6/30 18:26 + */ + void add(SysTimersParam sysTimersParam); + + /** + * 删除定时任务 + * + * @param sysTimersParam 删除参数 + * @author stylefeng + * @date 2020/6/30 18:26 + */ + void delete(SysTimersParam sysTimersParam); + + /** + * 编辑定时任务 + * + * @param sysTimersParam 编辑参数 + * @author stylefeng + * @date 2020/6/30 18:26 + */ + void edit(SysTimersParam sysTimersParam); + + /** + * 查看详情定时任务 + * + * @param sysTimersParam 查看参数 + * @return 定时任务 + * @author stylefeng + * @date 2020/6/30 18:26 + */ + SysTimers detail(SysTimersParam sysTimersParam); + + /** + * 启动任务 + * + * @param sysTimersParam 启动参数 + * @author stylefeng + * @date 2020/7/1 14:36 + */ + void start(SysTimersParam sysTimersParam); + + /** + * 停止任务 + * + * @param sysTimersParam 停止参数 + * @author stylefeng + * @date 2020/7/1 14:36 + */ + void stop(SysTimersParam sysTimersParam); + + /** + * 获取所有可执行的任务列表 + * + * @return TimerTaskRunner的所有子类名称集合 + * @author stylefeng + * @date 2020/7/1 14:36 + */ + List getActionClasses(); + +} diff --git a/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/service/impl/SysTimersServiceImpl.java b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/service/impl/SysTimersServiceImpl.java new file mode 100644 index 000000000..29d28d5f8 --- /dev/null +++ b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/service/impl/SysTimersServiceImpl.java @@ -0,0 +1,193 @@ +package cn.stylefeng.roses.kernel.timer.modular.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.cron.CronUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.stylefeng.roses.kernel.timer.modular.mapper.SysTimersMapper; +import cn.stylefeng.roses.kernel.timer.modular.param.SysTimersParam; +import cn.stylefeng.roses.kernel.timer.modular.service.SysTimersService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.timer.api.TimerAction; +import cn.stylefeng.roses.kernel.timer.api.TimerExeService; +import cn.stylefeng.roses.kernel.timer.api.enums.TimerJobStatusEnum; +import cn.stylefeng.roses.kernel.timer.api.exception.TimerException; +import cn.stylefeng.roses.kernel.timer.api.exception.enums.TimerExceptionEnum; +import cn.stylefeng.roses.kernel.timer.modular.entity.SysTimers; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 定时任务 服务实现类 + * + * @author stylefeng + * @date 2020/6/30 18:26 + */ +@Service +public class SysTimersServiceImpl extends ServiceImpl implements SysTimersService { + + @Resource + private TimerExeService timerExeService; + + @Override + public PageResult page(SysTimersParam sysTimersParam) { + + // 构造条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysTimersParam)) { + // 拼接查询条件-任务名称 + if (ObjectUtil.isNotEmpty(sysTimersParam.getTimerName())) { + queryWrapper.like(SysTimers::getTimerName, sysTimersParam.getTimerName()); + } + // 拼接查询条件-状态(字典 1运行 2停止) + if (ObjectUtil.isNotEmpty(sysTimersParam.getJobStatus())) { + queryWrapper.like(SysTimers::getJobStatus, sysTimersParam.getJobStatus()); + } + } + + // 查询分页结果 + Page page = this.page(PageFactory.defaultPage(), queryWrapper); + return PageResultFactory.createPageResult(page); + } + + @Override + public List list(SysTimersParam sysTimersParam) { + + // 构造条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysTimersParam)) { + // 拼接查询条件-任务名称 + if (ObjectUtil.isNotEmpty(sysTimersParam.getTimerName())) { + queryWrapper.like(SysTimers::getTimerName, sysTimersParam.getTimerName()); + } + // 拼接查询条件-状态(字典 1运行 2停止) + if (ObjectUtil.isNotEmpty(sysTimersParam.getJobStatus())) { + queryWrapper.like(SysTimers::getJobStatus, sysTimersParam.getJobStatus()); + } + } + + return this.list(queryWrapper); + } + + @Override + public void add(SysTimersParam sysTimersParam) { + + // 将dto转为实体 + SysTimers sysTimers = new SysTimers(); + BeanUtil.copyProperties(sysTimersParam, sysTimers); + + // 设置为停止状态,点击启动时启动任务 + sysTimers.setJobStatus(TimerJobStatusEnum.STOP.getCode()); + + this.save(sysTimers); + } + + @Override + public void delete(SysTimersParam sysTimersParam) { + + // 先停止id为参数id的定时器 + CronUtil.remove(String.valueOf(sysTimersParam.getId())); + + this.removeById(sysTimersParam.getId()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void edit(SysTimersParam sysTimersParam) { + + // 更新库中记录 + SysTimers oldTimer = this.querySysTimers(sysTimersParam); + BeanUtil.copyProperties(sysTimersParam, oldTimer); + this.updateById(oldTimer); + + // 查看被编辑的任务的状态 + Integer jobStatus = oldTimer.getJobStatus(); + + // 如果任务正在运行,则停掉这个任务,从新运行任务 + if (jobStatus.equals(TimerJobStatusEnum.RUNNING.getCode())) { + CronUtil.remove(String.valueOf(oldTimer.getId())); + timerExeService.startTimer( + String.valueOf(sysTimersParam.getId()), + sysTimersParam.getCron(), + sysTimersParam.getActionClass()); + } + } + + @Override + public SysTimers detail(SysTimersParam sysTimersParam) { + return this.querySysTimers(sysTimersParam); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void start(SysTimersParam sysTimersParam) { + + // 更新库中的状态 + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.set(SysTimers::getJobStatus, TimerJobStatusEnum.RUNNING.getCode()) + .eq(SysTimers::getId, sysTimersParam.getId()); + this.update(wrapper); + + // 添加定时任务调度 + SysTimers sysTimers = this.querySysTimers(sysTimersParam); + timerExeService.startTimer(String.valueOf(sysTimers.getId()), sysTimers.getCron(), sysTimers.getActionClass()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void stop(SysTimersParam sysTimersParam) { + + // 更新库中的状态 + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.set(SysTimers::getJobStatus, TimerJobStatusEnum.STOP.getCode()) + .eq(SysTimers::getId, sysTimersParam.getId()); + this.update(wrapper); + + // 关闭定时任务调度 + SysTimers sysTimers = this.querySysTimers(sysTimersParam); + timerExeService.stopTimer(String.valueOf(sysTimers.getId())); + } + + @Override + public List getActionClasses() { + + // 获取spring容器中的这类bean + Map timerActionMap = SpringUtil.getBeansOfType(TimerAction.class); + if (ObjectUtil.isNotEmpty(timerActionMap)) { + Collection values = timerActionMap.values(); + return values.stream().map(i -> i.getClass().getName()).collect(Collectors.toList()); + } else { + return CollectionUtil.newArrayList(); + } + } + + /** + * 获取定时任务详情 + * + * @author stylefeng + * @date 2020/6/30 18:26 + */ + private SysTimers querySysTimers(SysTimersParam sysTimersParam) { + SysTimers sysTimers = this.getById(sysTimersParam.getId()); + if (ObjectUtil.isEmpty(sysTimers)) { + String userTip = StrUtil.format(TimerExceptionEnum.JOB_DETAIL_NOT_FOUND.getUserTip(), sysTimersParam.getId()); + throw new TimerException(TimerExceptionEnum.JOB_DETAIL_NOT_FOUND, userTip); + } + return sysTimers; + } + +} diff --git a/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/tasks/SystemOutTaskRunner.java b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/tasks/SystemOutTaskRunner.java new file mode 100644 index 000000000..91c6306fd --- /dev/null +++ b/kernel-d-timer/timer-business/src/main/java/cn/stylefeng/roses/kernel/timer/modular/tasks/SystemOutTaskRunner.java @@ -0,0 +1,20 @@ +package cn.stylefeng.roses.kernel.timer.modular.tasks; + +import cn.stylefeng.roses.kernel.timer.api.TimerAction; +import org.springframework.stereotype.Component; + +/** + * 这是一个定时任务的示例程序 + * + * @author stylefeng + * @date 2020/6/30 22:09 + */ +@Component +public class SystemOutTaskRunner implements TimerAction { + + @Override + public void action() { + System.out.println("这是一个定时任务测试的程序,一直输出这行内容!"); + } + +} diff --git a/kernel-d-timer/timer-sdk-hutool/README.md b/kernel-d-timer/timer-sdk-hutool/README.md new file mode 100644 index 000000000..aaf2eb0c8 --- /dev/null +++ b/kernel-d-timer/timer-sdk-hutool/README.md @@ -0,0 +1 @@ +定时任务模块的sdk,基于hutool的定时任务实现 \ No newline at end of file diff --git a/kernel-d-timer/timer-sdk-hutool/pom.xml b/kernel-d-timer/timer-sdk-hutool/pom.xml new file mode 100644 index 000000000..a74f03e8d --- /dev/null +++ b/kernel-d-timer/timer-sdk-hutool/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-timer + 1.0.0 + ../pom.xml + + + timer-sdk-hutool + + jar + + + + + + cn.stylefeng.roses + timer-api + 1.0.0 + + + + + diff --git a/kernel-d-timer/timer-sdk-hutool/src/main/java/cn/stylefeng/roses/kernel/hutool/HutoolTimerExeServiceImpl.java b/kernel-d-timer/timer-sdk-hutool/src/main/java/cn/stylefeng/roses/kernel/hutool/HutoolTimerExeServiceImpl.java new file mode 100644 index 000000000..7d9c7f44e --- /dev/null +++ b/kernel-d-timer/timer-sdk-hutool/src/main/java/cn/stylefeng/roses/kernel/hutool/HutoolTimerExeServiceImpl.java @@ -0,0 +1,70 @@ +package cn.stylefeng.roses.kernel.hutool; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.cron.CronUtil; +import cn.hutool.cron.task.Task; +import cn.hutool.extra.spring.SpringUtil; +import cn.stylefeng.roses.kernel.timer.api.TimerAction; +import cn.stylefeng.roses.kernel.timer.api.TimerExeService; +import cn.stylefeng.roses.kernel.timer.api.exception.TimerException; +import cn.stylefeng.roses.kernel.timer.api.exception.enums.TimerExceptionEnum; +import lombok.extern.slf4j.Slf4j; + +/** + * hutool方式的定时任务执行 + * + * @author fengshuonan + * @date 2020/10/27 14:05 + */ +@Slf4j +public class HutoolTimerExeServiceImpl implements TimerExeService { + + @Override + public void startTimer(String taskId, String cron, String className) { + + // 判断任务id是否为空 + if (StrUtil.isBlank(taskId)) { + String userTip = StrUtil.format(TimerExceptionEnum.PARAM_HAS_NULL.getUserTip(), "taskId"); + throw new TimerException(TimerExceptionEnum.PARAM_HAS_NULL, userTip); + } + + // 判断任务cron表达式是否为空 + if (StrUtil.isBlank(cron)) { + String userTip = StrUtil.format(TimerExceptionEnum.PARAM_HAS_NULL.getUserTip(), "cron"); + throw new TimerException(TimerExceptionEnum.PARAM_HAS_NULL, userTip); + } + + // 判断类名称是否为空 + if (StrUtil.isBlank(className)) { + String userTip = StrUtil.format(TimerExceptionEnum.PARAM_HAS_NULL.getUserTip(), "className"); + throw new TimerException(TimerExceptionEnum.PARAM_HAS_NULL, userTip); + } + + // 预加载类看是否存在此定时任务类 + try { + Class.forName(className); + } catch (ClassNotFoundException e) { + String userTip = StrUtil.format(TimerExceptionEnum.CLASS_NOT_FOUND.getUserTip(), className); + throw new TimerException(TimerExceptionEnum.CLASS_NOT_FOUND, userTip); + } + + // 定义hutool的任务 + Task task = () -> { + try { + TimerAction timerAction = (TimerAction) SpringUtil.getBean(Class.forName(className)); + timerAction.action(); + } catch (ClassNotFoundException e) { + log.error(">>> 任务执行异常:{}", e.getMessage()); + } + }; + + // 开始执行任务 + CronUtil.schedule(taskId, cron, task); + } + + @Override + public void stopTimer(String taskId) { + CronUtil.remove(taskId); + } + +} diff --git a/kernel-d-timer/timer-spring-boot-starter/README.md b/kernel-d-timer/timer-spring-boot-starter/README.md new file mode 100644 index 000000000..cdd037b03 --- /dev/null +++ b/kernel-d-timer/timer-spring-boot-starter/README.md @@ -0,0 +1 @@ +定时任务的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-timer/timer-spring-boot-starter/pom.xml b/kernel-d-timer/timer-spring-boot-starter/pom.xml new file mode 100644 index 000000000..16a8b425e --- /dev/null +++ b/kernel-d-timer/timer-spring-boot-starter/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-timer + 1.0.0 + ../pom.xml + + + timer-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + timer-business + 1.0.0 + + + + + cn.stylefeng.roses + timer-sdk-hutool + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-timer/timer-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/timer/starter/GunsTimerAutoConfiguration.java b/kernel-d-timer/timer-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/timer/starter/GunsTimerAutoConfiguration.java new file mode 100644 index 000000000..335e40dab --- /dev/null +++ b/kernel-d-timer/timer-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/timer/starter/GunsTimerAutoConfiguration.java @@ -0,0 +1,30 @@ +package cn.stylefeng.roses.kernel.timer.starter; + +import cn.stylefeng.roses.kernel.hutool.HutoolTimerExeServiceImpl; +import cn.stylefeng.roses.kernel.timer.api.TimerExeService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 定时任务的自动配置 + * + * @author fengshuonan + * @date 2020/12/1 21:34 + */ +@Configuration +public class GunsTimerAutoConfiguration { + + /** + * hutool的定时任务 + * + * @author fengshuonan + * @date 2020/12/1 21:18 + */ + @Bean + @ConditionalOnMissingBean(TimerExeService.class) + public TimerExeService timerExeService() { + return new HutoolTimerExeServiceImpl(); + } + +} diff --git a/kernel-d-timer/timer-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-timer/timer-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..f409a39e5 --- /dev/null +++ b/kernel-d-timer/timer-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.timer.starter.GunsTimerAutoConfiguration \ No newline at end of file diff --git a/kernel-d-validator/README.md b/kernel-d-validator/README.md new file mode 100644 index 000000000..e45970e9e --- /dev/null +++ b/kernel-d-validator/README.md @@ -0,0 +1 @@ +参数校验模块 \ No newline at end of file diff --git a/kernel-d-validator/pom.xml b/kernel-d-validator/pom.xml new file mode 100644 index 000000000..da078422c --- /dev/null +++ b/kernel-d-validator/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-d-validator + + pom + + + validator-api + validator-sdk-count + validator-sdk-black-white + validator-business-count + validator-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-d-validator/validator-api/README.md b/kernel-d-validator/validator-api/README.md new file mode 100644 index 000000000..f852498ba --- /dev/null +++ b/kernel-d-validator/validator-api/README.md @@ -0,0 +1 @@ +配置模块的api模块 \ No newline at end of file diff --git a/kernel-d-validator/validator-api/pom.xml b/kernel-d-validator/validator-api/pom.xml new file mode 100644 index 000000000..4a2664d4c --- /dev/null +++ b/kernel-d-validator/validator-api/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-validator + 1.0.0 + ../pom.xml + + + validator-api + + jar + + + + + + + cn.stylefeng.roses + db-api + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/BlackListApi.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/BlackListApi.java new file mode 100644 index 000000000..cb55dbb47 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/BlackListApi.java @@ -0,0 +1,53 @@ +package cn.stylefeng.roses.kernel.validator; + + +import java.util.Collection; + +/** + * 黑名单Api + *

+ * 在黑名单的用户会被禁止访问程序 + * + * @author fengshuonan + * @date 2020/11/15 16:31 + */ +public interface BlackListApi { + + /** + * 添加名单条目 + * + * @param content 黑名单的一条内容,可以是ip,用户id之类的 + * @author fengshuonan + * @date 2020/11/15 16:32 + */ + void addBlackItem(String content); + + /** + * 删除名单条目 + * + * @param content 黑名单的一条内容,可以是ip,用户id之类的 + * @author fengshuonan + * @date 2020/11/15 16:32 + */ + void removeBlackItem(String content); + + /** + * 获取整个黑名单 + * + * @return 黑名单内容的列表 + * @author fengshuonan + * @date 2020/11/15 16:33 + */ + Collection getBlackList(); + + /** + * 是否包含某个值 + * + * @param content 黑名单的一条内容,可以是ip,用户id之类的 + * @return true-包含值,false-不包含值 + * @author fengshuonan + * @date 2020/11/20 16:55 + */ + boolean contains(String content); + +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/CountValidatorApi.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/CountValidatorApi.java new file mode 100644 index 000000000..c37ba9fab --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/CountValidatorApi.java @@ -0,0 +1,28 @@ +package cn.stylefeng.roses.kernel.validator; + + +import cn.stylefeng.roses.kernel.validator.exception.CountValidateException; + +/** + * 计数and校验API + *

+ * 检验一个key(用户或某个IP等)在一个时间段内的操作次数是否超标 + * + * @author fengshuonan + * @date 2020/11/14 17:18 + */ +public interface CountValidatorApi { + + /** + * 记录key的操作次数加1,并校验次数是否在当前时间窗内超标 + * + * @param key 一个操作的唯一标识,例如API_COUNT_192.168.1.15 + * @param timeWindowSeconds 一个时间窗的范围,时间窗单位是秒 + * @param timeWindowMaxCount 一个时间窗内操作的次数限制 + * @throws CountValidateException 如果校验失败,会抛出本异常 + * @author fengshuonan + * @date 2020/11/14 17:27 + */ + void countAndValidate(String key, Long timeWindowSeconds, Long timeWindowMaxCount) throws CountValidateException; + +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/WhiteListApi.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/WhiteListApi.java new file mode 100644 index 000000000..9256d2336 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/WhiteListApi.java @@ -0,0 +1,53 @@ +package cn.stylefeng.roses.kernel.validator; + + +import java.util.Collection; + +/** + * 白名单Api + *

+ * 在白名单的用户不会进行被访问受限 + * + * @author fengshuonan + * @date 2020/11/15 16:31 + */ +public interface WhiteListApi { + + /** + * 添加名单条目 + * + * @param content 白名单的一条内容,可以是ip,用户id之类的 + * @author fengshuonan + * @date 2020/11/15 16:32 + */ + void addWhiteItem(String content); + + /** + * 删除名单条目 + * + * @param content 白名单的一条内容,可以是ip,用户id之类的 + * @author fengshuonan + * @date 2020/11/15 16:32 + */ + void removeWhiteItem(String content); + + /** + * 获取整个白名单 + * + * @return 白名单内容的列表 + * @author fengshuonan + * @date 2020/11/15 16:33 + */ + Collection getWhiteList(); + + /** + * 是否包含某个值 + * + * @param content 白名单的一条内容,可以是ip,用户id之类的 + * @return true-包含值,false-不包含值 + * @author fengshuonan + * @date 2020/11/20 16:55 + */ + boolean contains(String content); + +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/constants/ValidatorConstants.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/constants/ValidatorConstants.java new file mode 100644 index 000000000..775dedf45 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/constants/ValidatorConstants.java @@ -0,0 +1,56 @@ +package cn.stylefeng.roses.kernel.validator.constants; + +/** + * 校验器模块的常量 + * + * @author fengshuonan + * @date 2020/10/31 14:24 + */ +public interface ValidatorConstants { + + /** + * 校验器模块的名称 + */ + String VALIDATOR_MODULE_NAME = "kernel-d-validator"; + + /** + * 异常枚举的步进值 + */ + String VALIDATOR_EXCEPTION_STEP_CODE = "15"; + + /** + * 默认逻辑删除字段的字段名 + */ + String DEFAULT_LOGIC_DELETE_FIELD_NAME = "del_flag"; + + /** + * 默认逻辑删除字段的值 + */ + String DEFAULT_LOGIC_DELETE_FIELD_VALUE = "Y"; + + /** + * 计数校验用的 缓存前缀标识 + */ + String COUNT_VALIDATE_CACHE_KEY_PREFIX = "COUNT_VALIDATE"; + + /** + * 黑名单 缓存前缀标识 + */ + String BLACK_LIST_CACHE_KEY_PREFIX = "BLACK_LIST"; + + /** + * 白名单 缓存前缀标识 + */ + String WHITE_LIST_CACHE_KEY_PREFIX = "WHITE_LIST"; + + /** + * 计数校验用的 计数当时的秒数 + */ + String RECORD_TIME_SECONDS = "RECORD_TIME_SECONDS"; + + /** + * 计数校验用的 时间窗内的次数 + */ + String COUNT_NUMBER = "COUNT_NUMBER"; + +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/context/RequestGroupContext.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/context/RequestGroupContext.java new file mode 100644 index 000000000..604564c94 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/context/RequestGroupContext.java @@ -0,0 +1,43 @@ +package cn.stylefeng.roses.kernel.validator.context; + +/** + * 保存控制器的方法上的校验组,group class + * + * @author fengshuonan + * @date 2020/11/4 14:31 + */ +public class RequestGroupContext { + + private static final ThreadLocal> GROUP_CLASS_HOLDER = new ThreadLocal<>(); + + /** + * 设置临时的校验分组 + * + * @author fengshuonan + * @date 2020/11/4 14:32 + */ + public static void set(Class groupValue) { + GROUP_CLASS_HOLDER.set(groupValue); + } + + /** + * 获取临时校验分组 + * + * @author fengshuonan + * @date 2020/11/4 14:32 + */ + public static Class get() { + return GROUP_CLASS_HOLDER.get(); + } + + /** + * 清除临时缓存的校验分组 + * + * @author fengshuonan + * @date 2020/11/4 14:32 + */ + public static void clear() { + GROUP_CLASS_HOLDER.remove(); + } + +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/context/RequestParamIdContext.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/context/RequestParamIdContext.java new file mode 100644 index 000000000..e8248c052 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/context/RequestParamIdContext.java @@ -0,0 +1,45 @@ +package cn.stylefeng.roses.kernel.validator.context; + +/** + * 临时保存参数id字段值,用于唯一性校验 + *

+ * 注意:如果要用@TableUniqueValue这个校验,必须得主键的字段名是id,否则会校验失败 + * + * @author fengshuonan + * @date 2020/11/4 14:34 + */ +public class RequestParamIdContext { + + private static final ThreadLocal PARAM_ID_HOLDER = new ThreadLocal<>(); + + /** + * 设置临时缓存的id + * + * @author fengshuonan + * @date 2020/11/4 14:35 + */ + public static void set(Long id) { + PARAM_ID_HOLDER.set(id); + } + + /** + * 获取临时缓存的id + * + * @author fengshuonan + * @date 2020/11/4 14:35 + */ + public static Long get() { + return PARAM_ID_HOLDER.get(); + } + + /** + * 清除临时缓存的id + * + * @author fengshuonan + * @date 2020/11/4 14:35 + */ + public static void clear() { + PARAM_ID_HOLDER.remove(); + } + +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/CountValidateException.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/CountValidateException.java new file mode 100644 index 000000000..c127e16da --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/CountValidateException.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.validator.exception; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; +import cn.stylefeng.roses.kernel.validator.constants.ValidatorConstants; + +/** + * 计数器校验异常 + * + * @author fengshuonan + * @date 2020/11/14 17:53 + */ +public class CountValidateException extends ServiceException { + + public CountValidateException(AbstractExceptionEnum exception, String userTip) { + super(ValidatorConstants.VALIDATOR_MODULE_NAME, exception.getErrorCode(), userTip); + } + + public CountValidateException(AbstractExceptionEnum exception) { + super(ValidatorConstants.VALIDATOR_MODULE_NAME, exception); + } + +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/ParamValidateException.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/ParamValidateException.java new file mode 100644 index 000000000..ed7827ca1 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/ParamValidateException.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.validator.exception; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; +import cn.stylefeng.roses.kernel.validator.constants.ValidatorConstants; + +/** + * 参数校验异常 + * + * @author fengshuonan + * @date 2020/10/15 15:59 + */ +public class ParamValidateException extends ServiceException { + + public ParamValidateException(AbstractExceptionEnum exception, String userTip) { + super(ValidatorConstants.VALIDATOR_MODULE_NAME, exception.getErrorCode(), userTip); + } + + public ParamValidateException(AbstractExceptionEnum exception) { + super(ValidatorConstants.VALIDATOR_MODULE_NAME, exception); + } + +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/enums/CountValidateExceptionEnum.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/enums/CountValidateExceptionEnum.java new file mode 100644 index 000000000..e13c77d59 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/enums/CountValidateExceptionEnum.java @@ -0,0 +1,42 @@ +package cn.stylefeng.roses.kernel.validator.exception.enums; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import cn.stylefeng.roses.kernel.validator.constants.ValidatorConstants; +import lombok.Getter; + +/** + * 计数器校验 + * + * @author fengshuonan + * @date 2020/11/14 17:54 + */ +@Getter +public enum CountValidateExceptionEnum implements AbstractExceptionEnum { + + /** + * 中断执行 + */ + INTERRUPT_EXECUTION(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ValidatorConstants.VALIDATOR_EXCEPTION_STEP_CODE + "01", "满足自定义策略要求,程序已中断执行!"), + + /** + * 限流错误 + */ + TRAFFIC_LIMIT_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ValidatorConstants.VALIDATOR_EXCEPTION_STEP_CODE + "02", "已触发限流机制,请稍后重新访问!"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + CountValidateExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/enums/ValidatorExceptionEnum.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/enums/ValidatorExceptionEnum.java new file mode 100644 index 000000000..7f532a8e4 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/exception/enums/ValidatorExceptionEnum.java @@ -0,0 +1,47 @@ +package cn.stylefeng.roses.kernel.validator.exception.enums; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import cn.stylefeng.roses.kernel.validator.constants.ValidatorConstants; +import lombok.Getter; + +/** + * 参数校验错误 + * + * @author fengshuonan + * @date 2020/10/16 10:53 + */ +@Getter +public enum ValidatorExceptionEnum implements AbstractExceptionEnum { + + /** + * 参数错误 + */ + PARAM_VALIDATE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ValidatorConstants.VALIDATOR_EXCEPTION_STEP_CODE + "01", "参数校验失败,请检查参数的传值是否正确,具体信息:{}"), + + /** + * 中断执行 + */ + INTERRUPT_EXECUTION(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ValidatorConstants.VALIDATOR_EXCEPTION_STEP_CODE + "02", "满足自定义策略要求,程序已中断执行!"), + + /** + * 数据库字段值唯一性校验出错,参数不完整 + */ + TABLE_UNIQUE_VALIDATE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + ValidatorConstants.VALIDATOR_EXCEPTION_STEP_CODE + "03", "数据库字段值唯一性校验出错,参数不完整,字段存在空值:{}"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + ValidatorExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/pojo/UniqueValidateParam.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/pojo/UniqueValidateParam.java new file mode 100644 index 000000000..044deada7 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/pojo/UniqueValidateParam.java @@ -0,0 +1,56 @@ +package cn.stylefeng.roses.kernel.validator.pojo; + +import lombok.Builder; +import lombok.Data; + +/** + * 校验参数时用的方法参数 + * + * @author fengshuonan + * @date 2020/8/17 21:43 + */ +@Data +@Builder +public class UniqueValidateParam { + + /** + * 表名称 + */ + String tableName; + + /** + * 列名称 + */ + String columnName; + + /** + * 被参数校验时候的字段的值 + */ + String value; + + /** + * 校验时,是否排除当前的记录 + */ + Boolean excludeCurrentRecord; + + /** + * 当前记录的主键id + */ + Long id; + + /** + * 排除所有被逻辑删除的记录的控制 + */ + Boolean excludeLogicDeleteItems; + + /** + * 逻辑删除的字段名 + */ + String logicDeleteFieldName; + + /** + * 逻辑删除的字段的值 + */ + String logicDeleteValue; + +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/utils/ValidatorUtil.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/utils/ValidatorUtil.java new file mode 100644 index 000000000..565d096c8 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/utils/ValidatorUtil.java @@ -0,0 +1,96 @@ +package cn.stylefeng.roses.kernel.validator.utils; + +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.validator.exception.ParamValidateException; +import cn.stylefeng.roses.kernel.validator.exception.enums.ValidatorExceptionEnum; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; +import java.util.Iterator; +import java.util.Set; + +/** + * 参数校验器,静态方法调用 + *

+ * 手动验证带有校验注解的参数是否合法 + * + * @author fengshuonan + * @date 2020/10/31 14:13 + */ +public class ValidatorUtil { + + /** + * 验证器实例 + */ + private static final Validator VALIDATOR_INSTANCE = Validation.buildDefaultValidatorFactory().getValidator(); + + /** + * 校验参数是否合法,返回校验的结果 + * + * @param object 被校验的包装类参数 + * @param groups 校验组 + * @return 参数校验的结果,为ConstraintViolation的集合 + * @author fengshuonan + * @date 2020/10/31 14:13 + */ + public static Set> validate(Object object, Class... groups) { + return VALIDATOR_INSTANCE.validate(object, groups); + } + + /** + * 校验参数是否合法,直接返回成功和失败 + * + * @param object 被校验的包装类参数 + * @param groups 校验组 + * @return true-参数合法,false-参数不合法 + * @author fengshuonan + * @date 2020/10/31 14:13 + */ + public static boolean simpleValidate(Object object, Class... groups) { + Set> constraintViolations = VALIDATOR_INSTANCE.validate(object, groups); + return constraintViolations.isEmpty(); + } + + /** + * 校验参数是否合法,不返回结果,有问题直接抛出异常 + * + * @param object 被校验的包装类参数 + * @param groups 校验组 + * @author fengshuonan + * @date 2020/10/31 14:13 + */ + public static void validateThrowMessage(Object object, Class... groups) { + String errorMessage = validateGetMessage(object, groups); + if (errorMessage != null) { + throw new ParamValidateException(ValidatorExceptionEnum.PARAM_VALIDATE_ERROR, errorMessage); + } + } + + /** + * 校验参数是否合法 + *

+ * 不合法会返回不合法的提示,合法的话会返回null + * + * @param object 被校验的包装类参数 + * @param groups 校验组 + * @author fengshuonan + * @date 2020/10/31 14:13 + */ + public static String validateGetMessage(Object object, Class... groups) { + Set> constraintViolations = VALIDATOR_INSTANCE.validate(object, groups); + if (!constraintViolations.isEmpty()) { + StringBuilder errorMessage = new StringBuilder(); + for (Iterator> it = constraintViolations.iterator(); it.hasNext(); ) { + ConstraintViolation violation = it.next(); + errorMessage.append(violation.getMessage()); + if (it.hasNext()) { + errorMessage.append(", "); + } + } + return StrUtil.format(ValidatorExceptionEnum.PARAM_VALIDATE_ERROR.getUserTip(), errorMessage); + } + return null; + } + +} \ No newline at end of file diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/date/DateValue.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/date/DateValue.java new file mode 100644 index 000000000..018455226 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/date/DateValue.java @@ -0,0 +1,53 @@ +package cn.stylefeng.roses.kernel.validator.validators.date; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 日期格式的校验,根据format参数的格式校验 + *

+ * format可以填写: + *

+ * yyyy-MM-dd + *

+ * yyyy-MM-dd HH:mm:ss + * + * @author fengshuonan + * @date 2020/11/18 21:27 + */ +@Documented +@Constraint(validatedBy = DateValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DateValue { + + String message() default "日期格式不正确,正确格式应为yyyy-MM-dd"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + /** + * 日期校验的格式,默认 yyyy-MM-dd + */ + String format() default "yyyy-MM-dd"; + + /** + * 是否必填 + *

+ * 如果必填,在校验的时候本字段没值就会报错 + */ + boolean required() default true; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + + DateValue[] value(); + + } +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/date/DateValueValidator.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/date/DateValueValidator.java new file mode 100644 index 000000000..80711259c --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/date/DateValueValidator.java @@ -0,0 +1,43 @@ +package cn.stylefeng.roses.kernel.validator.validators.date; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 日期校验格式,通过format的参数来校验格式 + * + * @author fengshuonan + * @date 2020/11/18 21:30 + */ +public class DateValueValidator implements ConstraintValidator { + + private Boolean required; + + private String format; + + @Override + public void initialize(DateValue constraintAnnotation) { + this.required = constraintAnnotation.required(); + this.format = constraintAnnotation.format(); + } + + @Override + public boolean isValid(String dateValue, ConstraintValidatorContext context) { + + // 如果是必填的 + if (required && StrUtil.isEmpty(dateValue)) { + return false; + } + + try { + // 校验格式 + DateUtil.parse(dateValue, format); + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/flag/FlagValue.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/flag/FlagValue.java new file mode 100644 index 000000000..ed3433ad3 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/flag/FlagValue.java @@ -0,0 +1,40 @@ +package cn.stylefeng.roses.kernel.validator.validators.flag; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 校验标识,只有Y和N两种状态的标识 + * + * @author fengshuonan + * @date 2020/10/31 14:53 + */ +@Documented +@Constraint(validatedBy = FlagValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface FlagValue { + + String message() default "不正确的flag标识,请传递Y或者N"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + /** + * 是否必填 + *

+ * 如果必填,在校验的时候本字段没值就会报错 + */ + boolean required() default true; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + FlagValue[] value(); + } +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/flag/FlagValueValidator.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/flag/FlagValueValidator.java new file mode 100644 index 000000000..5b6dc5762 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/flag/FlagValueValidator.java @@ -0,0 +1,40 @@ +package cn.stylefeng.roses.kernel.validator.validators.flag; + +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 校验标识,只有Y和N两种状态的标识 + * + * @author fengshuonan + * @date 2020/10/31 14:53 + */ +public class FlagValueValidator implements ConstraintValidator { + + private Boolean required; + + @Override + public void initialize(FlagValue constraintAnnotation) { + this.required = constraintAnnotation.required(); + } + + @Override + public boolean isValid(String flagValue, ConstraintValidatorContext context) { + + // 如果是必填的 + if (required) { + return YesOrNotEnum.Y.getCode().equals(flagValue) || YesOrNotEnum.N.getCode().equals(flagValue); + } else { + + //如果不是必填,可以为空 + if (StrUtil.isEmpty(flagValue)) { + return true; + } else { + return YesOrNotEnum.Y.getCode().equals(flagValue) || YesOrNotEnum.N.getCode().equals(flagValue); + } + } + } +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/status/StatusValue.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/status/StatusValue.java new file mode 100644 index 000000000..86e759c8d --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/status/StatusValue.java @@ -0,0 +1,41 @@ +package cn.stylefeng.roses.kernel.validator.validators.status; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 状态校验,校验参数的状态是否是 StatusEnum 中的值 + * + * @author fengshuonan + * @date 2020/10/31 13:56 + */ +@Documented +@Constraint(validatedBy = StatusValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface StatusValue { + + String message() default "不正确的状态标识,请检查status相关的值"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + /** + * 是否必填 + *

+ * 如果必填,在校验的时候本字段没值就会报错 + */ + boolean required() default true; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + StatusValue[] value(); + } + +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/status/StatusValueValidator.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/status/StatusValueValidator.java new file mode 100644 index 000000000..3e52fcee7 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/status/StatusValueValidator.java @@ -0,0 +1,41 @@ +package cn.stylefeng.roses.kernel.validator.validators.status; + +import cn.stylefeng.roses.kernel.rule.enums.StatusEnum; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 校验状态,判断是否为 StatusEnum 中的值 + * + * @author fengshuonan + * @date 2020/10/31 14:52 + */ +public class StatusValueValidator implements ConstraintValidator { + + private Boolean required; + + @Override + public void initialize(StatusValue constraintAnnotation) { + this.required = constraintAnnotation.required(); + } + + @Override + public boolean isValid(Integer statusValue, ConstraintValidatorContext context) { + + // 如果是必填的 + if (required && statusValue == null) { + return false; + } + + // 如果不是必填,为空的话就通过 + if (!required && statusValue == null) { + return true; + } + + // 校验值是否是枚举中的值 + StatusEnum statusEnum = StatusEnum.codeToEnum(statusValue); + return statusEnum != null; + + } +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/unique/TableUniqueValue.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/unique/TableUniqueValue.java new file mode 100644 index 000000000..84075b68c --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/unique/TableUniqueValue.java @@ -0,0 +1,66 @@ +package cn.stylefeng.roses.kernel.validator.validators.unique; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static cn.stylefeng.roses.kernel.validator.constants.ValidatorConstants.DEFAULT_LOGIC_DELETE_FIELD_NAME; +import static cn.stylefeng.roses.kernel.validator.constants.ValidatorConstants.DEFAULT_LOGIC_DELETE_FIELD_VALUE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 验证表的的某个字段值是否在是唯一值 + *

+ * 一般用来校验code字段,例如同一个表中,code字段不能重复 + * + * @author fengshuonan + * @date 2020/11/4 14:06 + */ +@Documented +@Constraint(validatedBy = TableUniqueValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface TableUniqueValue { + + String message() default "库中存在重复编码,请更换该编码值"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + /** + * 表名称,例如 sys_user + */ + String tableName(); + + /** + * 列名称,例如 user_code + */ + String columnName(); + + /** + * 是否开启逻辑删除校验,默认是关闭的 + *

+ * 关于为何开启逻辑删除校验: + *

+ * 若项目中某个表包含控制逻辑删除的字段,我们在进行唯一值校验的时候要排除这种状态的记录,所以需要用到这个功能 + */ + boolean excludeLogicDeleteItems() default false; + + /** + * 逻辑删除的字段名称 + */ + String logicDeleteFieldName() default DEFAULT_LOGIC_DELETE_FIELD_NAME; + + /** + * 默认逻辑删除的值(Y是已删除) + */ + String logicDeleteValue() default DEFAULT_LOGIC_DELETE_FIELD_VALUE; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + TableUniqueValue[] value(); + } +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/unique/TableUniqueValueValidator.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/unique/TableUniqueValueValidator.java new file mode 100644 index 000000000..8b4029886 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/unique/TableUniqueValueValidator.java @@ -0,0 +1,120 @@ +package cn.stylefeng.roses.kernel.validator.validators.unique; + +import cn.hutool.core.util.ObjectUtil; +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.validator.validators.unique.service.TableUniqueValueService; +import cn.stylefeng.roses.kernel.validator.context.RequestGroupContext; +import cn.stylefeng.roses.kernel.validator.context.RequestParamIdContext; +import cn.stylefeng.roses.kernel.validator.pojo.UniqueValidateParam; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 验证表的的某个字段值是否在是唯一值 + * + * @author fengshuonan + * @date 2020/11/4 14:39 + */ +public class TableUniqueValueValidator implements ConstraintValidator { + + /** + * 表名称,例如 sys_user + */ + private String tableName; + + /** + * 列名称,例如 user_code + */ + private String columnName; + + /** + * 是否开启逻辑删除校验,默认是关闭的 + *

+ * 关于为何开启逻辑删除校验: + *

+ * 若项目中某个表包含控制逻辑删除的字段,我们在进行唯一值校验的时候要排除这种状态的记录,所以需要用到这个功能 + */ + private boolean excludeLogicDeleteItems; + + /** + * 逻辑删除的字段名称 + */ + private String logicDeleteFieldName; + + /** + * 默认逻辑删除的值(Y是已删除) + */ + private String logicDeleteValue; + + @Override + public void initialize(TableUniqueValue constraintAnnotation) { + this.tableName = constraintAnnotation.tableName(); + this.columnName = constraintAnnotation.columnName(); + this.excludeLogicDeleteItems = constraintAnnotation.excludeLogicDeleteItems(); + this.logicDeleteFieldName = constraintAnnotation.logicDeleteFieldName(); + this.logicDeleteValue = constraintAnnotation.logicDeleteValue(); + } + + @Override + public boolean isValid(String fieldValue, ConstraintValidatorContext context) { + + if (ObjectUtil.isNull(fieldValue)) { + return true; + } + + Class validateGroupClass = RequestGroupContext.get(); + + // 如果属于add group,则校验库中所有行 + if (BaseRequest.add.class.equals(validateGroupClass)) { + UniqueValidateParam addParam = createAddParam(fieldValue); + return TableUniqueValueService.getFiledUniqueFlag(addParam); + } + + // 如果属于edit group,校验时需要排除当前修改的这条记录 + if (BaseRequest.edit.class.equals(validateGroupClass)) { + UniqueValidateParam editParam = createEditParam(fieldValue); + return TableUniqueValueService.getFiledUniqueFlag(editParam); + } + + // 默认校验所有的行 + UniqueValidateParam addParam = createAddParam(fieldValue); + return TableUniqueValueService.getFiledUniqueFlag(addParam); + } + + /** + * 创建校验新增的参数 + * + * @author fengshuonan + * @date 2020/8/17 21:55 + */ + private UniqueValidateParam createAddParam(String fieldValue) { + return UniqueValidateParam.builder() + .tableName(tableName) + .columnName(columnName) + .value(fieldValue) + .excludeCurrentRecord(Boolean.FALSE) + .excludeLogicDeleteItems(excludeLogicDeleteItems) + .logicDeleteFieldName(logicDeleteFieldName) + .logicDeleteValue(logicDeleteValue).build(); + } + + /** + * 创建修改的参数校验 + * + * @author fengshuonan + * @date 2020/8/17 21:56 + */ + private UniqueValidateParam createEditParam(String fieldValue) { + return UniqueValidateParam.builder() + .tableName(tableName) + .columnName(columnName) + .value(fieldValue) + .excludeCurrentRecord(Boolean.TRUE) + .id(RequestParamIdContext.get()) + .excludeLogicDeleteItems(excludeLogicDeleteItems) + .logicDeleteFieldName(logicDeleteFieldName) + .logicDeleteValue(logicDeleteValue).build(); + } + +} diff --git a/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/unique/service/TableUniqueValueService.java b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/unique/service/TableUniqueValueService.java new file mode 100644 index 000000000..82957b192 --- /dev/null +++ b/kernel-d-validator/validator-api/src/main/java/cn/stylefeng/roses/kernel/validator/validators/unique/service/TableUniqueValueService.java @@ -0,0 +1,125 @@ +package cn.stylefeng.roses.kernel.validator.validators.unique.service; + +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.db.api.DbOperatorApi; +import cn.stylefeng.roses.kernel.db.api.context.DbOperatorContext; +import cn.stylefeng.roses.kernel.validator.exception.ParamValidateException; +import cn.stylefeng.roses.kernel.validator.pojo.UniqueValidateParam; + +import static cn.stylefeng.roses.kernel.validator.exception.enums.ValidatorExceptionEnum.TABLE_UNIQUE_VALIDATE_ERROR; + +/** + * 判断表中字段是否是唯一值的业务封装 + * + * @author fengshuonan + * @date 2020/11/4 15:06 + */ +public class TableUniqueValueService { + + /** + * 判断表中某个字段是否已经存在该值 + * + * @author fengshuonan + * @date 2020/11/4 15:08 + */ + public static boolean getFiledUniqueFlag(UniqueValidateParam uniqueValidateParam) { + + DbOperatorApi dbOperatorApi = DbOperatorContext.me(); + + int resultCount = 0; + + // 参数校验 + paramValidate(uniqueValidateParam); + + // 不排除当前记录,不排除逻辑删除的内容 + if (!uniqueValidateParam.getExcludeCurrentRecord() + && !uniqueValidateParam.getExcludeLogicDeleteItems()) { + resultCount = dbOperatorApi.selectCount( + "select count(*) from " + uniqueValidateParam.getTableName() + " where " + uniqueValidateParam.getColumnName() + " = {0}", + uniqueValidateParam.getValue()); + } + + // 不排除当前记录,排除逻辑删除的内容 + if (!uniqueValidateParam.getExcludeCurrentRecord() + && uniqueValidateParam.getExcludeLogicDeleteItems()) { + resultCount = dbOperatorApi.selectCount( + "select count(*) from " + uniqueValidateParam.getTableName() + + " where " + uniqueValidateParam.getColumnName() + " = {0} " + + " and " + + "(" + uniqueValidateParam.getLogicDeleteFieldName() + " is null || " + + uniqueValidateParam.getLogicDeleteFieldName() + " <> " + uniqueValidateParam.getLogicDeleteValue() + ")", + uniqueValidateParam.getValue()); + } + + // 排除当前记录,不排除逻辑删除的内容 + if (uniqueValidateParam.getExcludeCurrentRecord() + && !uniqueValidateParam.getExcludeLogicDeleteItems()) { + + // id判空 + paramIdValidate(uniqueValidateParam); + + resultCount = dbOperatorApi.selectCount( + "select count(*) from " + uniqueValidateParam.getTableName() + + " where " + uniqueValidateParam.getColumnName() + " = {0} " + + " and id <> {1}", + uniqueValidateParam.getValue(), uniqueValidateParam.getId()); + } + + // 排除当前记录,排除逻辑删除的内容 + if (uniqueValidateParam.getExcludeCurrentRecord() + && uniqueValidateParam.getExcludeLogicDeleteItems()) { + + // id判空 + paramIdValidate(uniqueValidateParam); + + resultCount = dbOperatorApi.selectCount( + "select count(*) from " + uniqueValidateParam.getTableName() + + " where " + uniqueValidateParam.getColumnName() + " = {0} " + + " and id <> {1} " + + " and " + + "(" + uniqueValidateParam.getLogicDeleteFieldName() + " is null || " + + uniqueValidateParam.getLogicDeleteFieldName() + " <> " + uniqueValidateParam.getLogicDeleteValue() + ")", + uniqueValidateParam.getValue(), uniqueValidateParam.getId()); + } + + // 如果大于0,代表不是唯一的当前校验的值 + return resultCount <= 0; + + + } + + /** + * 几个参数的为空校验 + * + * @author fengshuonan + * @date 2020/11/4 15:11 + */ + private static void paramValidate(UniqueValidateParam uniqueValidateParam) { + if (StrUtil.isBlank(uniqueValidateParam.getTableName())) { + String userTip = StrUtil.format(TABLE_UNIQUE_VALIDATE_ERROR.getUserTip(), "tableName表名"); + throw new ParamValidateException(TABLE_UNIQUE_VALIDATE_ERROR, userTip); + } + if (StrUtil.isBlank(uniqueValidateParam.getColumnName())) { + String userTip = StrUtil.format(TABLE_UNIQUE_VALIDATE_ERROR.getUserTip(), "columnName字段名"); + throw new ParamValidateException(TABLE_UNIQUE_VALIDATE_ERROR, userTip); + } + if (StrUtil.isBlank(uniqueValidateParam.getValue())) { + String userTip = StrUtil.format(TABLE_UNIQUE_VALIDATE_ERROR.getUserTip(), "字段值"); + throw new ParamValidateException(TABLE_UNIQUE_VALIDATE_ERROR, userTip); + } + } + + /** + * id参数的为空校验 + * + * @author fengshuonan + * @date 2020/11/4 15:16 + */ + private static void paramIdValidate(UniqueValidateParam uniqueValidateParam) { + if (uniqueValidateParam.getId() == null) { + String userTip = StrUtil.format(TABLE_UNIQUE_VALIDATE_ERROR.getUserTip(), "id为空"); + throw new ParamValidateException(TABLE_UNIQUE_VALIDATE_ERROR, userTip); + } + } + +} diff --git a/kernel-d-validator/validator-business-count/README.md b/kernel-d-validator/validator-business-count/README.md new file mode 100644 index 000000000..ea87336af --- /dev/null +++ b/kernel-d-validator/validator-business-count/README.md @@ -0,0 +1 @@ +在线维护校验规则,校验规则包含,时间窗内接口请求次数的维护等等 TODO 待编写 \ No newline at end of file diff --git a/kernel-d-validator/validator-business-count/pom.xml b/kernel-d-validator/validator-business-count/pom.xml new file mode 100644 index 000000000..782f3bfdc --- /dev/null +++ b/kernel-d-validator/validator-business-count/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-validator + 1.0.0 + ../pom.xml + + + validator-business-count + + jar + + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + cn.stylefeng.roses + config-sdk-db + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/controller/package-info.java b/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/controller/package-info.java new file mode 100644 index 000000000..f6d3ed6fd --- /dev/null +++ b/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/controller/package-info.java @@ -0,0 +1 @@ +package cn.stylefeng.roses.kernel.validator.modular.controller; \ No newline at end of file diff --git a/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/entity/package-info.java b/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/entity/package-info.java new file mode 100644 index 000000000..47dc7f9ae --- /dev/null +++ b/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/entity/package-info.java @@ -0,0 +1 @@ +package cn.stylefeng.roses.kernel.validator.modular.entity; \ No newline at end of file diff --git a/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/enums/package-info.java b/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/enums/package-info.java new file mode 100644 index 000000000..fb761d533 --- /dev/null +++ b/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/enums/package-info.java @@ -0,0 +1 @@ +package cn.stylefeng.roses.kernel.validator.modular.enums; \ No newline at end of file diff --git a/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/mapper/package-info.java b/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/mapper/package-info.java new file mode 100644 index 000000000..c2f13563a --- /dev/null +++ b/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/mapper/package-info.java @@ -0,0 +1 @@ +package cn.stylefeng.roses.kernel.validator.modular.mapper; \ No newline at end of file diff --git a/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/param/package-info.java b/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/param/package-info.java new file mode 100644 index 000000000..5d43d0cee --- /dev/null +++ b/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/param/package-info.java @@ -0,0 +1 @@ +package cn.stylefeng.roses.kernel.validator.modular.param; \ No newline at end of file diff --git a/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/service/package-info.java b/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/service/package-info.java new file mode 100644 index 000000000..8ad84812c --- /dev/null +++ b/kernel-d-validator/validator-business-count/src/main/java/cn/stylefeng/roses/kernel/validator/modular/service/package-info.java @@ -0,0 +1 @@ +package cn.stylefeng.roses.kernel.validator.modular.service; \ No newline at end of file diff --git a/kernel-d-validator/validator-sdk-black-white/README.md b/kernel-d-validator/validator-sdk-black-white/README.md new file mode 100644 index 000000000..c303d92a1 --- /dev/null +++ b/kernel-d-validator/validator-sdk-black-white/README.md @@ -0,0 +1 @@ +黑白名单的校验模块 \ No newline at end of file diff --git a/kernel-d-validator/validator-sdk-black-white/pom.xml b/kernel-d-validator/validator-sdk-black-white/pom.xml new file mode 100644 index 000000000..5a815b842 --- /dev/null +++ b/kernel-d-validator/validator-sdk-black-white/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-validator + 1.0.0 + + + validator-sdk-black-white + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + cn.stylefeng.roses + cache-api + 1.0.0 + + + cn.stylefeng.roses + cache-sdk-memory + 1.0.0 + true + + + cn.stylefeng.roses + cache-sdk-redis + 1.0.0 + true + + + + \ No newline at end of file diff --git a/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/BlackListService.java b/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/BlackListService.java new file mode 100644 index 000000000..d8e5b8398 --- /dev/null +++ b/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/BlackListService.java @@ -0,0 +1,44 @@ +package cn.stylefeng.roses.kemel.blackwhite; + +import cn.stylefeng.roses.kernel.cache.api.CacheOperatorApi; +import cn.stylefeng.roses.kernel.validator.BlackListApi; + +import java.util.Collection; + +/** + * 黑名单的实现 + *

+ * 黑名单的数据会在访问资源时被限制 + * + * @author fengshuonan + * @date 2020/11/20 15:52 + */ +public class BlackListService implements BlackListApi { + + private final CacheOperatorApi cacheOperatorApi; + + public BlackListService(CacheOperatorApi cacheOperatorApi) { + this.cacheOperatorApi = cacheOperatorApi; + } + + @Override + public void addBlackItem(String content) { + cacheOperatorApi.put(content, content); + } + + @Override + public void removeBlackItem(String content) { + cacheOperatorApi.remove(content); + } + + @Override + public Collection getBlackList() { + return cacheOperatorApi.getAllKeys(); + } + + @Override + public boolean contains(String content) { + return cacheOperatorApi.contains(content); + } + +} diff --git a/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/WhiteListService.java b/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/WhiteListService.java new file mode 100644 index 000000000..d28eea7db --- /dev/null +++ b/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/WhiteListService.java @@ -0,0 +1,44 @@ +package cn.stylefeng.roses.kemel.blackwhite; + +import cn.stylefeng.roses.kernel.cache.api.CacheOperatorApi; +import cn.stylefeng.roses.kernel.validator.WhiteListApi; + +import java.util.Collection; + +/** + * 白名单的实现 + *

+ * 白名单的数据在访问资源时不受限 + * + * @author fengshuonan + * @date 2020/11/20 15:53 + */ +public class WhiteListService implements WhiteListApi { + + private final CacheOperatorApi cacheOperatorApi; + + public WhiteListService(CacheOperatorApi cacheOperatorApi) { + this.cacheOperatorApi = cacheOperatorApi; + } + + @Override + public void addWhiteItem(String content) { + cacheOperatorApi.put(content, content); + } + + @Override + public void removeWhiteItem(String content) { + cacheOperatorApi.remove(content); + } + + @Override + public Collection getWhiteList() { + return cacheOperatorApi.getAllKeys(); + } + + @Override + public boolean contains(String content) { + return cacheOperatorApi.contains(content); + } + +} diff --git a/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/BlackListMemoryCache.java b/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/BlackListMemoryCache.java new file mode 100644 index 000000000..aaffbc225 --- /dev/null +++ b/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/BlackListMemoryCache.java @@ -0,0 +1,25 @@ +package cn.stylefeng.roses.kemel.blackwhite.cache; + +import cn.hutool.cache.impl.TimedCache; +import cn.stylefeng.roses.kernel.cache.AbstractMemoryCacheOperator; + +import static cn.stylefeng.roses.kernel.validator.constants.ValidatorConstants.BLACK_LIST_CACHE_KEY_PREFIX; + +/** + * 黑名单用户的缓存 + * + * @author fengshuonan + * @date 2020/11/20 15:50 + */ +public class BlackListMemoryCache extends AbstractMemoryCacheOperator { + + public BlackListMemoryCache(TimedCache timedCache) { + super(timedCache); + } + + @Override + public String getCommonKeyPrefix() { + return BLACK_LIST_CACHE_KEY_PREFIX; + } + +} diff --git a/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/BlackListRedisCache.java b/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/BlackListRedisCache.java new file mode 100644 index 000000000..19108f5cd --- /dev/null +++ b/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/BlackListRedisCache.java @@ -0,0 +1,25 @@ +package cn.stylefeng.roses.kemel.blackwhite.cache; + +import cn.stylefeng.roses.kernel.cache.AbstractRedisCacheOperator; +import org.springframework.data.redis.core.RedisTemplate; + +import static cn.stylefeng.roses.kernel.validator.constants.ValidatorConstants.BLACK_LIST_CACHE_KEY_PREFIX; + +/** + * 黑名单用户的缓存 + * + * @author fengshuonan + * @date 2020/11/20 15:50 + */ +public class BlackListRedisCache extends AbstractRedisCacheOperator { + + public BlackListRedisCache(RedisTemplate redisTemplate) { + super(redisTemplate); + } + + @Override + public String getCommonKeyPrefix() { + return BLACK_LIST_CACHE_KEY_PREFIX; + } + +} diff --git a/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/WhiteListMemoryCache.java b/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/WhiteListMemoryCache.java new file mode 100644 index 000000000..910ef0668 --- /dev/null +++ b/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/WhiteListMemoryCache.java @@ -0,0 +1,25 @@ +package cn.stylefeng.roses.kemel.blackwhite.cache; + +import cn.hutool.cache.impl.TimedCache; +import cn.stylefeng.roses.kernel.cache.AbstractMemoryCacheOperator; + +import static cn.stylefeng.roses.kernel.validator.constants.ValidatorConstants.WHITE_LIST_CACHE_KEY_PREFIX; + +/** + * 白名单的缓存 + * + * @author fengshuonan + * @date 2020/11/15 15:26 + */ +public class WhiteListMemoryCache extends AbstractMemoryCacheOperator { + + public WhiteListMemoryCache(TimedCache timedCache) { + super(timedCache); + } + + @Override + public String getCommonKeyPrefix() { + return WHITE_LIST_CACHE_KEY_PREFIX; + } + +} diff --git a/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/WhiteListRedisCache.java b/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/WhiteListRedisCache.java new file mode 100644 index 000000000..9db0734c4 --- /dev/null +++ b/kernel-d-validator/validator-sdk-black-white/src/main/java/cn/stylefeng/roses/kemel/blackwhite/cache/WhiteListRedisCache.java @@ -0,0 +1,25 @@ +package cn.stylefeng.roses.kemel.blackwhite.cache; + +import cn.hutool.cache.impl.TimedCache; +import cn.stylefeng.roses.kernel.cache.AbstractMemoryCacheOperator; + +import static cn.stylefeng.roses.kernel.validator.constants.ValidatorConstants.WHITE_LIST_CACHE_KEY_PREFIX; + +/** + * 白名单的缓存 + * + * @author fengshuonan + * @date 2020/11/15 15:26 + */ +public class WhiteListRedisCache extends AbstractMemoryCacheOperator { + + public WhiteListRedisCache(TimedCache timedCache) { + super(timedCache); + } + + @Override + public String getCommonKeyPrefix() { + return WHITE_LIST_CACHE_KEY_PREFIX; + } + +} diff --git a/kernel-d-validator/validator-sdk-count/README.md b/kernel-d-validator/validator-sdk-count/README.md new file mode 100644 index 000000000..622b59e27 --- /dev/null +++ b/kernel-d-validator/validator-sdk-count/README.md @@ -0,0 +1 @@ +计数校验模块,可以对任何key进行计数和校验,例如接口访问次数,ip访问次数 \ No newline at end of file diff --git a/kernel-d-validator/validator-sdk-count/pom.xml b/kernel-d-validator/validator-sdk-count/pom.xml new file mode 100644 index 000000000..676f8fdbe --- /dev/null +++ b/kernel-d-validator/validator-sdk-count/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-validator + 1.0.0 + + + validator-sdk-count + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + cn.stylefeng.roses + cache-api + 1.0.0 + + + cn.stylefeng.roses + cache-sdk-memory + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + \ No newline at end of file diff --git a/kernel-d-validator/validator-sdk-count/src/main/java/cn/stylefeng/roses/kemel/count/DefaultCountValidator.java b/kernel-d-validator/validator-sdk-count/src/main/java/cn/stylefeng/roses/kemel/count/DefaultCountValidator.java new file mode 100644 index 000000000..6fbd497b3 --- /dev/null +++ b/kernel-d-validator/validator-sdk-count/src/main/java/cn/stylefeng/roses/kemel/count/DefaultCountValidator.java @@ -0,0 +1,73 @@ +package cn.stylefeng.roses.kemel.count; + +import cn.hutool.core.convert.Convert; +import cn.stylefeng.roses.kernel.cache.api.CacheOperatorApi; +import cn.stylefeng.roses.kernel.validator.CountValidatorApi; +import cn.stylefeng.roses.kernel.validator.exception.CountValidateException; +import cn.stylefeng.roses.kernel.validator.exception.enums.CountValidateExceptionEnum; + +import static cn.stylefeng.roses.kernel.cache.api.constants.CacheConstants.CACHE_DELIMITER; +import static cn.stylefeng.roses.kernel.validator.constants.ValidatorConstants.*; + +/** + * 默认的计数校验器 + * + * @author fengshuonan + * @date 2020/11/15 12:14 + */ +public class DefaultCountValidator implements CountValidatorApi { + + private final CacheOperatorApi cacheOperatorApi; + + public DefaultCountValidator(CacheOperatorApi cacheOperatorApi) { + this.cacheOperatorApi = cacheOperatorApi; + } + + @Override + public synchronized void countAndValidate(String key, Long timeWindowSeconds, Long timeWindowMaxCount) throws CountValidateException { + + // 获取当前时间的秒数 + long currentTimeSeconds = System.currentTimeMillis() / 1000; + + // 上一次操作时间秒数的缓存key COUNT_VALIDATE:key:RECORD_SECONDS + String recordTimeSecondsKey = COUNT_VALIDATE_CACHE_KEY_PREFIX + CACHE_DELIMITER + key + CACHE_DELIMITER + RECORD_TIME_SECONDS; + + // 上一次执行次数的记录缓存key COUNT_VALIDATE:key:COUNT_NUMBER + String countNumberKey = COUNT_VALIDATE_CACHE_KEY_PREFIX + CACHE_DELIMITER + key + CACHE_DELIMITER + COUNT_NUMBER; + + // 获取缓存中上一次操作时间秒数 + Object recordTimeSecondsObject = cacheOperatorApi.get(recordTimeSecondsKey); + Long recordTimeSeconds = Convert.toLong(recordTimeSecondsObject); + if (recordTimeSeconds == null) { + recordTimeSeconds = currentTimeSeconds; + } + + // 获取缓存中上一次执行次数的记录 + Object countNumberObject = cacheOperatorApi.get(countNumberKey); + Long countNumber = Convert.toLong(countNumberObject); + if (countNumber == null) { + countNumber = 0L; + } + + // 当前时间和记录时间的差 超过限制的时间段就归零计数,否则就直接加1 + if ((currentTimeSeconds - recordTimeSeconds) == timeWindowSeconds) { + countNumber = 0L; + } else if ((currentTimeSeconds - recordTimeSeconds) > timeWindowSeconds) { + countNumber = 0L; + } else if ((currentTimeSeconds - recordTimeSeconds) < timeWindowSeconds) { + countNumber = countNumber + 1; + } else if (recordTimeSeconds.equals(currentTimeSeconds)) { + countNumber = countNumber + 1; + } + + cacheOperatorApi.put(recordTimeSecondsKey, currentTimeSeconds, timeWindowSeconds); + cacheOperatorApi.put(countNumberKey, countNumber, timeWindowSeconds); + + // 如果记录次数大于了时间窗内的最大容许值,则抛出异常 + if (countNumber > timeWindowMaxCount) { + throw new CountValidateException(CountValidateExceptionEnum.INTERRUPT_EXECUTION); + } + + } + +} diff --git a/kernel-d-validator/validator-sdk-count/src/main/java/cn/stylefeng/roses/kemel/count/cache/DefaultCountValidateCache.java b/kernel-d-validator/validator-sdk-count/src/main/java/cn/stylefeng/roses/kemel/count/cache/DefaultCountValidateCache.java new file mode 100644 index 000000000..d91a6cab5 --- /dev/null +++ b/kernel-d-validator/validator-sdk-count/src/main/java/cn/stylefeng/roses/kemel/count/cache/DefaultCountValidateCache.java @@ -0,0 +1,25 @@ +package cn.stylefeng.roses.kemel.count.cache; + +import cn.hutool.cache.impl.TimedCache; +import cn.stylefeng.roses.kernel.cache.AbstractMemoryCacheOperator; + +import static cn.stylefeng.roses.kernel.validator.constants.ValidatorConstants.COUNT_VALIDATE_CACHE_KEY_PREFIX; + +/** + * 计数用的缓存 + * + * @author fengshuonan + * @date 2020/11/15 15:26 + */ +public class DefaultCountValidateCache extends AbstractMemoryCacheOperator { + + public DefaultCountValidateCache(TimedCache timedCache) { + super(timedCache); + } + + @Override + public String getCommonKeyPrefix() { + return COUNT_VALIDATE_CACHE_KEY_PREFIX; + } + +} diff --git a/kernel-d-validator/validator-spring-boot-starter/README.md b/kernel-d-validator/validator-spring-boot-starter/README.md new file mode 100644 index 000000000..9d2b7b034 --- /dev/null +++ b/kernel-d-validator/validator-spring-boot-starter/README.md @@ -0,0 +1 @@ +校验器的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-d-validator/validator-spring-boot-starter/pom.xml b/kernel-d-validator/validator-spring-boot-starter/pom.xml new file mode 100644 index 000000000..24750365c --- /dev/null +++ b/kernel-d-validator/validator-spring-boot-starter/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-d-validator + 1.0.0 + ../pom.xml + + + validator-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + validator-business-count + 1.0.0 + + + + + cn.stylefeng.roses + validator-sdk-black-white + 1.0.0 + + + + + cn.stylefeng.roses + validator-sdk-count + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-d-validator/validator-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/validator/starter/GunsValidatorAutoConfiguration.java b/kernel-d-validator/validator-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/validator/starter/GunsValidatorAutoConfiguration.java new file mode 100644 index 000000000..598e6220b --- /dev/null +++ b/kernel-d-validator/validator-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/validator/starter/GunsValidatorAutoConfiguration.java @@ -0,0 +1,69 @@ +package cn.stylefeng.roses.kernel.validator.starter; + +import cn.hutool.cache.CacheUtil; +import cn.hutool.cache.impl.TimedCache; +import cn.stylefeng.roses.kemel.blackwhite.BlackListService; +import cn.stylefeng.roses.kemel.blackwhite.WhiteListService; +import cn.stylefeng.roses.kemel.blackwhite.cache.BlackListMemoryCache; +import cn.stylefeng.roses.kemel.blackwhite.cache.WhiteListMemoryCache; +import cn.stylefeng.roses.kemel.count.DefaultCountValidator; +import cn.stylefeng.roses.kemel.count.cache.DefaultCountValidateCache; +import cn.stylefeng.roses.kernel.validator.BlackListApi; +import cn.stylefeng.roses.kernel.validator.CountValidatorApi; +import cn.stylefeng.roses.kernel.validator.WhiteListApi; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 校验器自动配置 + * + * @author fengshuonan + * @date 2020/12/1 21:44 + */ +@Configuration +public class GunsValidatorAutoConfiguration { + + /** + * 黑名单校验 + * + * @author fengshuonan + * @date 2020/12/1 21:18 + */ + @Bean + @ConditionalOnMissingBean(BlackListApi.class) + public BlackListApi blackListApi() { + TimedCache timedCache = CacheUtil.newTimedCache(1000L * 3600 * 24 * 999); + BlackListMemoryCache blackListMemoryCache = new BlackListMemoryCache(timedCache); + return new BlackListService(blackListMemoryCache); + } + + /** + * 计数校验器 + * + * @author fengshuonan + * @date 2020/12/1 21:18 + */ + @Bean + @ConditionalOnMissingBean(CountValidatorApi.class) + public CountValidatorApi countValidatorApi() { + TimedCache timedCache = CacheUtil.newTimedCache(1000L * 3600 * 24 * 999); + DefaultCountValidateCache defaultCountValidateCache = new DefaultCountValidateCache(timedCache); + return new DefaultCountValidator(defaultCountValidateCache); + } + + /** + * 白名单校验 + * + * @author fengshuonan + * @date 2020/12/1 21:18 + */ + @Bean + @ConditionalOnMissingBean(WhiteListApi.class) + public WhiteListApi whiteListApi() { + TimedCache timedCache = CacheUtil.newTimedCache(1000L * 3600 * 24 * 999); + WhiteListMemoryCache whiteListMemoryCache = new WhiteListMemoryCache(timedCache); + return new WhiteListService(whiteListMemoryCache); + } + +} diff --git a/kernel-d-validator/validator-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-d-validator/validator-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..6632a9974 --- /dev/null +++ b/kernel-d-validator/validator-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.validator.starter.GunsValidatorAutoConfiguration \ No newline at end of file diff --git a/kernel-s-demo/README.md b/kernel-s-demo/README.md new file mode 100644 index 000000000..e1e99401b --- /dev/null +++ b/kernel-s-demo/README.md @@ -0,0 +1,3 @@ +demo模块是一个业务模块,demo是演示环境的意思 + +这个模块用在系统可能需要存在演示环境时使用,演示环境禁止用户操作数据库,只能查看某些数据 \ No newline at end of file diff --git a/kernel-s-demo/demo-api/README.md b/kernel-s-demo/demo-api/README.md new file mode 100644 index 000000000..07b9a749e --- /dev/null +++ b/kernel-s-demo/demo-api/README.md @@ -0,0 +1 @@ +demo模块的api \ No newline at end of file diff --git a/kernel-s-demo/demo-api/pom.xml b/kernel-s-demo/demo-api/pom.xml new file mode 100644 index 000000000..abc9bbe4a --- /dev/null +++ b/kernel-s-demo/demo-api/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-demo + 1.0.0 + ../pom.xml + + + demo-api + + jar + + + + + + cn.stylefeng.roses + config-api + 1.0.0 + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/constants/DemoConstants.java b/kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/constants/DemoConstants.java new file mode 100644 index 000000000..ec09eaa89 --- /dev/null +++ b/kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/constants/DemoConstants.java @@ -0,0 +1,21 @@ +package cn.stylefeng.roses.kernel.demo.constants; + +/** + * demo模块的常量 + * + * @author fengshuonan + * @date 2020/10/16 11:05 + */ +public interface DemoConstants { + + /** + * demo模块的名称 + */ + String DEMO_MODULE_NAME = "kernel-s-demo"; + + /** + * 异常枚举的步进值 + */ + String DEMO_EXCEPTION_STEP_CODE = "05"; + +} diff --git a/kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/exception/DemoException.java b/kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/exception/DemoException.java new file mode 100644 index 000000000..b6eb308b6 --- /dev/null +++ b/kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/exception/DemoException.java @@ -0,0 +1,19 @@ +package cn.stylefeng.roses.kernel.demo.exception; + +import cn.stylefeng.roses.kernel.demo.constants.DemoConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; + +/** + * 演示环境操作异常 + * + * @author fengshuonan + * @date 2020/10/15 15:59 + */ +public class DemoException extends ServiceException { + + public DemoException(AbstractExceptionEnum exception) { + super(DemoConstants.DEMO_MODULE_NAME, exception); + } + +} diff --git a/kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/exception/enums/DemoExceptionEnum.java b/kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/exception/enums/DemoExceptionEnum.java new file mode 100644 index 000000000..1ca567b4f --- /dev/null +++ b/kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/exception/enums/DemoExceptionEnum.java @@ -0,0 +1,37 @@ +package cn.stylefeng.roses.kernel.demo.exception.enums; + +import cn.stylefeng.roses.kernel.demo.constants.DemoConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import lombok.Getter; + +/** + * 演示环境操作的异常枚举 + * + * @author fengshuonan + * @date 2020/10/16 10:53 + */ +@Getter +public enum DemoExceptionEnum implements AbstractExceptionEnum { + + /** + * 演示环境无法操作 + */ + DEMO_OPERATE(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DemoConstants.DEMO_EXCEPTION_STEP_CODE + "01", "演示环境无法操作!"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + DemoExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/expander/DemoConfigExpander.java b/kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/expander/DemoConfigExpander.java new file mode 100644 index 000000000..b78a7f12f --- /dev/null +++ b/kernel-s-demo/demo-api/src/main/java/cn/stylefeng/roses/kernel/demo/expander/DemoConfigExpander.java @@ -0,0 +1,24 @@ +package cn.stylefeng.roses.kernel.demo.expander; + +import cn.stylefeng.roses.kernel.config.api.context.ConfigContext; + +/** + * 演示环境相关的配置拓展 + * + * @author fengshuonan + * @date 2020/10/17 16:10 + */ +public class DemoConfigExpander { + + /** + * 获取演示环境是否开启,默认是关闭的 + * + * @return true-开启,false-关闭 + * @author fengshuonan + * @date 2020/10/17 16:12 + */ + public static Boolean getDemoEnvFlag() { + return ConfigContext.me().getSysConfigValueWithDefault("SYS_DEMO_ENV_FLAG", Boolean.class, false); + } + +} diff --git a/kernel-s-demo/demo-business/README.md b/kernel-s-demo/demo-business/README.md new file mode 100644 index 000000000..384b759d7 --- /dev/null +++ b/kernel-s-demo/demo-business/README.md @@ -0,0 +1 @@ +demo模块的具体实现 \ No newline at end of file diff --git a/kernel-s-demo/demo-business/pom.xml b/kernel-s-demo/demo-business/pom.xml new file mode 100644 index 000000000..e2a2bd7bc --- /dev/null +++ b/kernel-s-demo/demo-business/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-demo + 1.0.0 + ../pom.xml + + + demo-business + + jar + + + + + + cn.stylefeng.roses + demo-api + 1.0.0 + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + diff --git a/kernel-s-demo/demo-business/src/main/java/cn/stylefeng/roses/kernel/demo/DemoProfileSqlInterceptor.java b/kernel-s-demo/demo-business/src/main/java/cn/stylefeng/roses/kernel/demo/DemoProfileSqlInterceptor.java new file mode 100644 index 000000000..951635449 --- /dev/null +++ b/kernel-s-demo/demo-business/src/main/java/cn/stylefeng/roses/kernel/demo/DemoProfileSqlInterceptor.java @@ -0,0 +1,59 @@ +package cn.stylefeng.roses.kernel.demo; + +import cn.stylefeng.roses.kernel.demo.expander.DemoConfigExpander; +import com.baomidou.mybatisplus.core.toolkit.PluginUtils; +import cn.stylefeng.roses.kernel.auth.api.expander.AuthConfigExpander; +import cn.stylefeng.roses.kernel.demo.exception.DemoException; +import cn.stylefeng.roses.kernel.demo.exception.enums.DemoExceptionEnum; +import cn.stylefeng.roses.kernel.rule.util.HttpServletUtil; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; +import org.springframework.util.AntPathMatcher; + +import java.sql.Connection; + +/** + * 演示环境的sql过滤器,只放开select语句,其他语句都不放过 + * + * @author stylefeng + * @date 2020/5/5 12:21 + */ +@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) +public class DemoProfileSqlInterceptor implements Interceptor { + + @Override + public Object intercept(Invocation invocation) throws Throwable { + + // 演示环境没开,直接跳过此过滤器 + if (!DemoConfigExpander.getDemoEnvFlag()) { + return invocation.proceed(); + } + + StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget()); + MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler); + MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement"); + + if (SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) { + return invocation.proceed(); + } else { + + //放开不进行安全过滤的接口 + for (String notAuthResource : AuthConfigExpander.getNoneSecurityConfig()) { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + if (antPathMatcher.match(notAuthResource, HttpServletUtil.getRequest().getRequestURI())) { + return invocation.proceed(); + } + } + + throw new DemoException(DemoExceptionEnum.DEMO_OPERATE); + } + } + +} diff --git a/kernel-s-demo/demo-spring-boot-starter/README.md b/kernel-s-demo/demo-spring-boot-starter/README.md new file mode 100644 index 000000000..cbc8e7b49 --- /dev/null +++ b/kernel-s-demo/demo-spring-boot-starter/README.md @@ -0,0 +1 @@ +演示环境的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-s-demo/demo-spring-boot-starter/pom.xml b/kernel-s-demo/demo-spring-boot-starter/pom.xml new file mode 100644 index 000000000..f7e2c39f8 --- /dev/null +++ b/kernel-s-demo/demo-spring-boot-starter/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-demo + 1.0.0 + ../pom.xml + + + demo-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + demo-business + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-s-demo/demo-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/demo/starter/GunsDemoAutoConfiguration.java b/kernel-s-demo/demo-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/demo/starter/GunsDemoAutoConfiguration.java new file mode 100644 index 000000000..652dd677b --- /dev/null +++ b/kernel-s-demo/demo-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/demo/starter/GunsDemoAutoConfiguration.java @@ -0,0 +1,30 @@ +package cn.stylefeng.roses.kernel.demo.starter; + +import cn.stylefeng.roses.kernel.demo.DemoProfileSqlInterceptor; +import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 演示环境的自动配置 + * + * @author fengshuonan + * @date 2020/12/1 21:51 + */ +@Configuration +@AutoConfigureAfter(MybatisPlusAutoConfiguration.class) +public class GunsDemoAutoConfiguration { + + /** + * 演示环境的sql拦截器 + * + * @author fengshuonan + * @date 2020/12/1 21:18 + */ + @Bean + public DemoProfileSqlInterceptor demoProfileSqlInterceptor() { + return new DemoProfileSqlInterceptor(); + } + +} diff --git a/kernel-s-demo/demo-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-s-demo/demo-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..debd6cb1b --- /dev/null +++ b/kernel-s-demo/demo-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.demo.starter.GunsDemoAutoConfiguration \ No newline at end of file diff --git a/kernel-s-demo/pom.xml b/kernel-s-demo/pom.xml new file mode 100644 index 000000000..989ed0d8b --- /dev/null +++ b/kernel-s-demo/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-s-demo + + pom + + + demo-api + demo-business + demo-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + javax.servlet + javax.servlet-api + + + + + diff --git a/kernel-s-dict/README.md b/kernel-s-dict/README.md new file mode 100644 index 000000000..15615b0d4 --- /dev/null +++ b/kernel-s-dict/README.md @@ -0,0 +1 @@ +字典管理模块,负责字典的维护 \ No newline at end of file diff --git a/kernel-s-dict/dict-api/README.md b/kernel-s-dict/dict-api/README.md new file mode 100644 index 000000000..7b41fbe13 --- /dev/null +++ b/kernel-s-dict/dict-api/README.md @@ -0,0 +1,3 @@ +字典模块的API + +定义了持久化字典的逻辑,留作以后扩展内存字典使用 \ No newline at end of file diff --git a/kernel-s-dict/dict-api/pom.xml b/kernel-s-dict/dict-api/pom.xml new file mode 100644 index 000000000..6f5f1d483 --- /dev/null +++ b/kernel-s-dict/dict-api/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-dict + 1.0.0 + ../pom.xml + + + dict-api + + jar + + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/DictApi.java b/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/DictApi.java new file mode 100644 index 000000000..10474e45a --- /dev/null +++ b/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/DictApi.java @@ -0,0 +1,11 @@ +package cn.stylefeng.roses.kernel.dict.api; + +/** + * 字典模块对外提供的api,方便其他模块直接调用 + * + * @author fengshuonan + * @date 2020/10/29 14:45 + */ +public interface DictApi { + +} diff --git a/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/constants/DictConstants.java b/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/constants/DictConstants.java new file mode 100644 index 000000000..3d423731e --- /dev/null +++ b/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/constants/DictConstants.java @@ -0,0 +1,26 @@ +package cn.stylefeng.roses.kernel.dict.api.constants; + +/** + * 字典模块的常量 + * + * @author fengshuonan + * @date 2020/10/29 11:38 + */ +public interface DictConstants { + + /** + * dict模块的名称 + */ + String DICT_MODULE_NAME = "kernel-s-dict"; + + /** + * 异常枚举的步进值 + */ + String DICT_EXCEPTION_STEP_CODE = "13"; + + /** + * 默认字典根节点的id + */ + Long DEFAULT_DICT_PARENT_ID = -1L; + +} \ No newline at end of file diff --git a/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/context/DictContext.java b/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/context/DictContext.java new file mode 100644 index 000000000..74fc7530a --- /dev/null +++ b/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/context/DictContext.java @@ -0,0 +1,24 @@ +package cn.stylefeng.roses.kernel.dict.api.context; + +import cn.hutool.extra.spring.SpringUtil; +import cn.stylefeng.roses.kernel.dict.api.DictApi; + +/** + * 字典模块,对外的api + * + * @author fengshuonan + * @date 2020/10/29 11:39 + */ +public class DictContext { + + /** + * 获取字典相关操作接口 + * + * @author fengshuonan + * @date 2020/10/29 11:55 + */ + public static DictApi me() { + return SpringUtil.getBean(DictApi.class); + } + +} diff --git a/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/enums/DictTypeClassEnum.java b/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/enums/DictTypeClassEnum.java new file mode 100644 index 000000000..a48328a8d --- /dev/null +++ b/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/enums/DictTypeClassEnum.java @@ -0,0 +1,30 @@ +package cn.stylefeng.roses.kernel.dict.api.enums; + +import lombok.Getter; + +/** + * 字典类型的分类枚举 + * + * @author fengshuonan + * @date 2020/10/30 10:19 + */ +@Getter +public enum DictTypeClassEnum { + + /** + * 字典类型为业务类型 + */ + BUSINESS_TYPE(1), + + /** + * 字典类型为系统类型 + */ + SYSTEM_TYPE(2); + + private final Integer code; + + DictTypeClassEnum(Integer code) { + this.code = code; + } + +} diff --git a/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/exception/DictException.java b/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/exception/DictException.java new file mode 100644 index 000000000..5bce75eaa --- /dev/null +++ b/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/exception/DictException.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.dict.api.exception; + +import cn.stylefeng.roses.kernel.dict.api.constants.DictConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; + +/** + * 字典模块的异常 + * + * @author fengshuonan + * @date 2020/10/29 11:57 + */ +public class DictException extends ServiceException { + + public DictException(AbstractExceptionEnum exception, String userTip) { + super(DictConstants.DICT_MODULE_NAME, exception.getErrorCode(), userTip); + } + + public DictException(AbstractExceptionEnum exception) { + super(DictConstants.DICT_MODULE_NAME, exception); + } + +} diff --git a/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/exception/enums/DictExceptionEnum.java b/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/exception/enums/DictExceptionEnum.java new file mode 100644 index 000000000..3fec6bf35 --- /dev/null +++ b/kernel-s-dict/dict-api/src/main/java/cn/stylefeng/roses/kernel/dict/api/exception/enums/DictExceptionEnum.java @@ -0,0 +1,67 @@ +package cn.stylefeng.roses.kernel.dict.api.exception.enums; + +import cn.stylefeng.roses.kernel.dict.api.constants.DictConstants; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import lombok.Getter; + +/** + * 字典模块相关的异常枚举 + * + * @author fengshuonan + * @date 2020/10/29 11:55 + */ +@Getter +public enum DictExceptionEnum implements AbstractExceptionEnum { + + /** + * 同类字典类型下,字典编码重复 + */ + DICT_CODE_REPEAT(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + DictConstants.DICT_EXCEPTION_STEP_CODE + "01", "同类字典类型下,字典编码重复,字典类型:{},字典编码:{}"), + + /** + * 同类字典类型下,字典名称重复 + */ + DICT_NAME_REPEAT(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + DictConstants.DICT_EXCEPTION_STEP_CODE + "02", "同类字典类型下,字典编码重复,字典类型:{},字典名称:{}"), + + /** + * 父级id不存在,输入的父级id不合理 + */ + PARENT_DICT_NOT_EXISTED(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + DictConstants.DICT_EXCEPTION_STEP_CODE + "03", "父级id不存在,输入的父级id不合理,父级id:{}"), + + /** + * 字典不存在 + */ + DICT_NOT_EXISTED(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DictConstants.DICT_EXCEPTION_STEP_CODE + "04", "字典不存在,字典id:{}"), + + /** + * 错误的字典状态 + */ + WRONG_DICT_STATUS(RuleConstants.BUSINESS_ERROR_TYPE_CODE + DictConstants.DICT_EXCEPTION_STEP_CODE + "05", "字典状态错误,字典状态:{}"), + + /** + * 字典类型编码重复 + */ + DICT_TYPE_CODE_REPEAT(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + DictConstants.DICT_EXCEPTION_STEP_CODE + "06", "字典类型编码重复,字典类型编码:{}"), + + /** + * 字典类型不存在 + */ + DICT_TYPE_NOT_EXISTED(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + DictConstants.DICT_EXCEPTION_STEP_CODE + "07", "字典类型不存在,字典类型id:{}"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + DictExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-s-dict/dict-business/README.md b/kernel-s-dict/dict-business/README.md new file mode 100644 index 000000000..5e147b8c2 --- /dev/null +++ b/kernel-s-dict/dict-business/README.md @@ -0,0 +1,3 @@ +字典业务模块 + +维护所有字典相关的业务操作 \ No newline at end of file diff --git a/kernel-s-dict/dict-business/pom.xml b/kernel-s-dict/dict-business/pom.xml new file mode 100644 index 000000000..9ecd16eea --- /dev/null +++ b/kernel-s-dict/dict-business/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-dict + 1.0.0 + ../pom.xml + + + dict-business + + jar + + + + + + cn.stylefeng.roses + dict-api + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/controller/DictController.java b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/controller/DictController.java new file mode 100644 index 000000000..5ebc245ae --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/controller/DictController.java @@ -0,0 +1,142 @@ +package cn.stylefeng.roses.kernel.dict.modular.controller; + +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.dict.modular.entity.Dict; +import cn.stylefeng.roses.kernel.dict.modular.pojo.TreeDictInfo; +import cn.stylefeng.roses.kernel.dict.modular.pojo.request.DictRequest; +import cn.stylefeng.roses.kernel.dict.modular.service.DictService; +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 字典详情管理,具体管理某个字典类型下的条目 + * + * @author fengshuonan + * @date 2020/10/29 14:45 + */ +@RestController +@ApiResource(name = "字典详情管理") +public class DictController { + + @Autowired + private DictService dictService; + + /** + * 添加字典条目 + * + * @author fengshuonan + * @date 2020/10/29 16:35 + */ + @PostResource(name = "添加字典", path = "/dict/addDict", requiredPermission = false) + public ResponseData addDictType(@RequestBody @Validated(DictRequest.add.class) DictRequest dictRequest) { + this.dictService.addDict(dictRequest); + return new SuccessResponseData(); + } + + /** + * 修改字典条目 + * + * @author fengshuonan + * @date 2020/10/29 16:35 + */ + @PostResource(name = "修改字典", path = "/dict/updateDict", requiredPermission = false) + public ResponseData updateDict(@RequestBody @Validated(DictRequest.edit.class) DictRequest dictRequest) { + this.dictService.updateDict(dictRequest); + return new SuccessResponseData(); + } + + /** + * 更新字典状态 + * + * @author fengshuonan + * @date 2020/10/29 16:35 + */ + @PostResource(name = "更新字典状态", path = "/dict/updateDictStatus", requiredPermission = false) + public ResponseData updateDictStatus(@RequestBody @Validated(BaseRequest.updateStatus.class) DictRequest dictRequest) { + this.dictService.updateDictStatus(dictRequest); + return new SuccessResponseData(); + } + + /** + * 删除字典条目 + * + * @author fengshuonan + * @date 2020/10/29 16:35 + */ + @PostResource(name = "删除字典", path = "/dict/deleteDict", requiredPermission = false) + public ResponseData deleteDict(@RequestBody @Validated(DictRequest.delete.class) DictRequest dictRequest) { + this.dictService.deleteDict(dictRequest); + return new SuccessResponseData(); + } + + /** + * 获取字典详情 + * + * @author fengshuonan + * @date 2020/10/29 16:35 + */ + @GetResource(name = "获取字典详情", path = "/dict/getDictDetail", requiredPermission = false) + public ResponseData getDictDetail(@RequestBody @Validated(DictRequest.detail.class) DictRequest dictRequest) { + Dict detail = this.dictService.findDetail(dictRequest); + return new SuccessResponseData(detail); + } + + /** + * 获取字典列表 + * + * @author fengshuonan + * @date 2020/10/29 16:35 + */ + @PostResource(name = "获取字典列表", path = "/dict/getDictList", requiredPermission = false) + public ResponseData getDictList(@RequestBody DictRequest dictRequest) { + List dictList = this.dictService.findList(dictRequest); + return new SuccessResponseData(dictList); + } + + /** + * 获取字典列表(分页) + * + * @author fengshuonan + * @date 2020/10/29 16:35 + */ + @PostResource(name = "获取字典列表", path = "/dict/getDictListPage", requiredPermission = false) + public ResponseData getDictListPage(@RequestBody DictRequest dictRequest) { + PageResult page = this.dictService.findPageList(dictRequest); + return new SuccessResponseData(page); + } + + /** + * 获取树形字典列表 + * + * @author fengshuonan + * @date 2020/10/29 16:36 + */ + @PostResource(name = "获取树形字典列表", path = "/dict/getDictTreeList", requiredPermission = false) + public ResponseData getDictTreeList(@RequestBody @Validated(DictRequest.treeList.class) DictRequest dictRequest) { + List treeDictList = this.dictService.getTreeDictList(dictRequest); + return new SuccessResponseData(treeDictList); + } + + /** + * code校验 + * + * @author fengshuonan + * @date 2020/10/29 16:36 + */ + @PostResource(name = "code校验", path = "/dict/validateCodeAvailable", requiredPermission = false) + public ResponseData validateCodeAvailable(@RequestBody @Validated(DictRequest.treeList.class) DictRequest dictRequest) { + boolean flag = this.dictService.validateCodeAvailable(dictRequest); + return new SuccessResponseData(flag); + } + +} diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/controller/DictTypeController.java b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/controller/DictTypeController.java new file mode 100644 index 000000000..be5d621d6 --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/controller/DictTypeController.java @@ -0,0 +1,120 @@ +package cn.stylefeng.roses.kernel.dict.modular.controller; + +import cn.stylefeng.roses.kernel.dict.modular.entity.DictType; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.dict.modular.pojo.request.DictTypeRequest; +import cn.stylefeng.roses.kernel.dict.modular.service.DictTypeService; +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + + +/** + * 字典类型管理 + * + * @author fengshuonan + * @date 2020/10/30 21:46 + */ +@RestController +@ApiResource(name = "字典类型管理") +public class DictTypeController { + + @Autowired + private DictTypeService dictTypeService; + + /** + * 添加字典类型 + * + * @author fengshuonan + * @Date 2018/7/25 下午12:36 + */ + @PostResource(name = "添加字典类型", path = "/dictType/addDictType", requiredPermission = false) + public ResponseData addDictType(@RequestBody @Validated(DictTypeRequest.add.class) DictTypeRequest dictTypeRequest) { + this.dictTypeService.addDictType(dictTypeRequest); + return new SuccessResponseData(); + } + + /** + * 修改字典类型 + * + * @author fengshuonan + * @Date 2018/7/25 下午12:36 + */ + @PostResource(name = "修改字典类型", path = "/dictType/updateDictType", requiredPermission = false) + public ResponseData updateDictType(@RequestBody @Validated(DictTypeRequest.edit.class) DictTypeRequest dictTypeRequest) { + this.dictTypeService.updateDictType(dictTypeRequest); + return new SuccessResponseData(); + } + + /** + * 修改字典类型状态 + * + * @author fengshuonan + * @Date 2018/7/25 下午12:36 + */ + @PostResource(name = "修改字典类型状态", path = "/dictType/updateStatus", requiredPermission = false) + public ResponseData updateStatus(@RequestBody @Validated(BaseRequest.updateStatus.class) DictTypeRequest dictTypeRequest) { + this.dictTypeService.updateDictTypeStatus(dictTypeRequest); + return new SuccessResponseData(); + } + + /** + * 删除字典类型 + * + * @author fengshuonan + * @Date 2018/7/25 下午12:36 + */ + @PostResource(name = "删除字典类型", path = "/dictType/deleteDictType", requiredPermission = false) + public ResponseData deleteDictType(@RequestBody @Validated(DictTypeRequest.delete.class) DictTypeRequest dictTypeRequest) { + this.dictTypeService.deleteDictType(dictTypeRequest); + return new SuccessResponseData(); + } + + /** + * 获取字典类型列表 + * + * @author fengshuonan + * @date 2020/10/30 21:46 + */ + @PostResource(name = "获取字典类型列表", path = "/dictType/getDictTypeList", requiredPermission = false) + public ResponseData getDictTypeList(@RequestBody DictTypeRequest dictTypeRequest) { + List dictTypeList = dictTypeService.getDictTypeList(dictTypeRequest); + return new SuccessResponseData(dictTypeList); + } + + /** + * 获取字典类型列表(分页) + * + * @author fengshuonan + * @date 2020/10/30 21:46 + */ + @PostResource(name = "获取字典类型列表(分页)", path = "/dictType/getDictTypePageList", requiredPermission = false) + public ResponseData getDictTypePageList(@RequestBody DictTypeRequest dictTypeRequest) { + Page page = PageFactory.defaultPage(); + PageResult dictTypePageList = dictTypeService.getDictTypePageList(page, dictTypeRequest); + return new SuccessResponseData(dictTypePageList); + } + + /** + * code校验 + * + * @author fengshuonan + * @date 2020/10/30 21:53 + */ + @PostResource(name = "code校验", path = "/dictType/validateCodeAvailable", requiredPermission = false) + public ResponseData validateCodeAvailable(@RequestBody DictTypeRequest dictTypeRequest) { + boolean flag = this.dictTypeService.validateCodeAvailable(dictTypeRequest); + return new SuccessResponseData(flag); + } + +} diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/entity/Dict.java b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/entity/Dict.java new file mode 100644 index 000000000..990b0a254 --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/entity/Dict.java @@ -0,0 +1,93 @@ +package cn.stylefeng.roses.kernel.dict.modular.entity; + +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 cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 字典实体 + * + * @author fengshuonan + * @date 2020/10/30 9:54 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_dict") +public class Dict extends BaseEntity { + + /** + * 字典id + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 字典编码 + */ + @TableField("dict_code") + private String dictCode; + + /** + * 字典名称 + */ + @TableField("dict_name") + private String dictName; + + /** + * 字典类型的编码 + */ + @TableField("dict_type_code") + private String dictTypeCode; + + /** + * 字典简称 + */ + @TableField("dict_short_name") + private String dictShortName; + + /** + * 字典简称的编码 + */ + @TableField("dict_short_code") + private String dictShortCode; + + /** + * 上级字典的id + *

+ * 字典列表是可以有树形结构的,但是字典类型没有树形结构 + *

+ * 如果没有上级字典id,则为-1 + */ + @TableField("parent_dict_id") + private Long parentDictId; + + /** + * 状态(1:启用,2:禁用),参考 StatusEnum + */ + @TableField("dict_status") + private Integer dictStatus; + + /** + * 删除标记 Y-已删除,N-未删除,参考 YesOrNotEnum + */ + @TableField("del_flag") + private String delFlag; + + /** + * 排序,带小数点 + */ + @TableField(value = "dict_sort") + private BigDecimal dictSort; + + /** + * 字典类型的名称 + */ + private transient String dictTypeName; + +} diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/entity/DictType.java b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/entity/DictType.java new file mode 100644 index 000000000..357a1fbe2 --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/entity/DictType.java @@ -0,0 +1,72 @@ +package cn.stylefeng.roses.kernel.dict.modular.entity; + +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 cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 字典类型表,一个字典类型下有多个字典 + * + * @author fengshuonan + * @date 2020/10/30 10:07 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_dict_type") +public class DictType extends BaseEntity { + + /** + * 字典类型id + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 字典类型: 1-业务类型,2-系统类型,参考 DictTypeClassEnum + */ + @TableField("dict_type_class") + private Integer dictTypeClass; + + /** + * 字典类型编码 + */ + @TableField("dict_type_code") + private String dictTypeCode; + + /** + * 字典类型名称 + */ + @TableField("dict_type_name") + private String dictTypeName; + + /** + * 字典类型描述 + */ + @TableField("dict_type_desc") + private String dictTypeDesc; + + /** + * 字典类型的状态:1-启用,2-禁用,参考 StatusEnum + */ + @TableField("dict_type_status") + private Integer dictTypeStatus; + + /** + * 删除标记 Y-已删除,N-未删除,参考 YesOrNotEnum + */ + @TableField("del_flag") + private String delFlag; + + /** + * 排序,带小数点 + */ + @TableField(value = "dict_type_sort") + private BigDecimal dictTypeSort; + +} diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/DictMapper.java b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/DictMapper.java new file mode 100644 index 000000000..03b91e1f1 --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/DictMapper.java @@ -0,0 +1,40 @@ +package cn.stylefeng.roses.kernel.dict.modular.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import cn.stylefeng.roses.kernel.dict.modular.entity.Dict; +import cn.stylefeng.roses.kernel.dict.modular.pojo.request.DictRequest; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 字典详情的管理 + * + * @author fengshuonan + * @date 2020/10/29 17:20 + */ +public interface DictMapper extends BaseMapper { + + /** + * 获取分页字典列表 + * + * @param dictRequest 查询条件对象 + * @return 字典列表 + * @author fengshuonan + * @date 2020/10/29 17:21 + */ + Dict findDetail(@Param("dictRequest") DictRequest dictRequest); + + /** + * 获取分页字典列表 + * + * @param page 分页参数 + * @param dictRequest 查询条件对象 + * @return 字典列表 + * @author fengshuonan + * @date 2020/10/29 17:21 + */ + List findList(Page page, @Param("dictRequest") DictRequest dictRequest); + +} diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/DictTypeMapper.java b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/DictTypeMapper.java new file mode 100644 index 000000000..3cb97b5dc --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/DictTypeMapper.java @@ -0,0 +1,30 @@ +package cn.stylefeng.roses.kernel.dict.modular.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import cn.stylefeng.roses.kernel.dict.modular.entity.DictType; +import cn.stylefeng.roses.kernel.dict.modular.pojo.request.DictTypeRequest; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 字典类型表 Mapper 接口 + * + * @author fengshuonan + * @date 2020/10/30 21:04 + */ +public interface DictTypeMapper extends BaseMapper { + + /** + * 获取字典类型列表 + * + * @param page 分页对象 + * @param dictTypeRequest 字典类型对象 + * @return 字典列表 + * @author fengshuonan + * @date 2020/10/29 17:41 + */ + List findList(Page page, @Param("dictTypeRequest") DictTypeRequest dictTypeRequest); + +} diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/mapping/DictMapper.xml b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/mapping/DictMapper.xml new file mode 100644 index 000000000..1d0faf127 --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/mapping/DictMapper.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/mapping/DictTypeMapper.xml b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/mapping/DictTypeMapper.xml new file mode 100644 index 000000000..7690ff8f7 --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/mapper/mapping/DictTypeMapper.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/pojo/TreeDictInfo.java b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/pojo/TreeDictInfo.java new file mode 100644 index 000000000..d6b0797f3 --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/pojo/TreeDictInfo.java @@ -0,0 +1,67 @@ +package cn.stylefeng.roses.kernel.dict.modular.pojo; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractTreeNode; +import lombok.Data; + +import java.util.List; + +/** + * 字典详细信息 + *

+ * 字典表的实现可以用内存,数据库或redis(具体实现可以根据业务需求自定义) + * + * @author fengshuonan + * @date 2020/10/30 11:05 + */ +@Data +public class TreeDictInfo implements AbstractTreeNode { + + /** + * 字典id + */ + private Long dictId; + + /** + * 字典编码 + */ + private String dictCode; + + /** + * 字典名称 + */ + private String dictName; + + /** + * 上级字典id + */ + private Long parentDictId; + + /** + * tree子节点 + */ + private List children; + + @Override + public String getNodeId() { + if (this.dictId == null) { + return null; + } else { + return this.dictId.toString(); + } + } + + @Override + public String getNodeParentId() { + if (this.parentDictId == null) { + return null; + } else { + return this.parentDictId.toString(); + } + } + + @Override + public void setChildrenNodes(List linkedList) { + this.children = linkedList; + } + +} diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/pojo/request/DictRequest.java b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/pojo/request/DictRequest.java new file mode 100644 index 000000000..f2e55cb53 --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/pojo/request/DictRequest.java @@ -0,0 +1,81 @@ +package cn.stylefeng.roses.kernel.dict.modular.pojo.request; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 字典请求参数封装 + * + * @author fengshuonan + * @date 2020/10/30 11:04 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class DictRequest extends BaseRequest { + + /** + * 字典id + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class, updateStatus.class}) + private Long id; + + /** + * 字典编码 + */ + @NotBlank(message = "字典编码不能为空", groups = {add.class, edit.class, validateAvailable.class}) + private String dictCode; + + /** + * 字典名称 + */ + @NotBlank(message = "字典名称不能为空", groups = {add.class, edit.class}) + private String dictName; + + /** + * 字典类型编码 + */ + @NotBlank(message = "字典类型编码不能为空", groups = {add.class, edit.class, treeList.class, validateAvailable.class}) + private String dictTypeCode; + + /** + * 字典简称 + */ + private String dictShortName; + + /** + * 字典简称的编码 + */ + private String dictShortCode; + + /** + * 上级字典的id + *

+ * 字典列表是可以有树形结构的,但是字典类型没有树形结构 + *

+ * 如果没有上级字典id,则为-1 + */ + private Long parentDictId; + + /** + * 状态(1:启用,2:禁用),参考 StatusEnum + */ + @NotNull(message = "状态不能为空", groups = {updateStatus.class, edit.class}) + private Integer dictStatus; + + /** + * 获取树形列表 + */ + public @interface treeList { + } + + /** + * 校验编码是否重复 + */ + public @interface validateAvailable { + } + +} diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/pojo/request/DictTypeRequest.java b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/pojo/request/DictTypeRequest.java new file mode 100644 index 000000000..a9bba4927 --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/pojo/request/DictTypeRequest.java @@ -0,0 +1,55 @@ +package cn.stylefeng.roses.kernel.dict.modular.pojo.request; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 字典类型,请求参数封装 + * + * @author fengshuonan + * @date 2020/10/30 11:05 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class DictTypeRequest extends BaseRequest { + + /** + * 字典类型id + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class, updateStatus.class}) + private Long id; + + /** + * 字典类型: 1-业务类型,2-系统类型,参考 DictTypeClassEnum + */ + @NotNull(message = "字典类型不能为空", groups = {add.class, edit.class}) + private Integer dictTypeClass; + + /** + * 字典类型编码 + */ + @NotBlank(message = "字典类型编码不能为空", groups = {add.class, edit.class}) + private String dictTypeCode; + + /** + * 字典类型名称 + */ + @NotBlank(message = "字典类型名称不能为空", groups = {add.class, edit.class}) + private String dictTypeName; + + /** + * 字典类型描述 + */ + private String dictTypeDesc; + + /** + * 字典类型的状态:1-启用,2-禁用,参考 StatusEnum + */ + @NotNull(message = "状态不能为空", groups = {updateStatus.class}) + private Integer dictTypeStatus; + +} diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/DictService.java b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/DictService.java new file mode 100644 index 000000000..92af05e5d --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/DictService.java @@ -0,0 +1,105 @@ +package cn.stylefeng.roses.kernel.dict.modular.service; + +import cn.stylefeng.roses.kernel.dict.modular.entity.Dict; +import cn.stylefeng.roses.kernel.dict.modular.pojo.TreeDictInfo; +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.dict.modular.pojo.request.DictRequest; + +import java.util.List; + +/** + * 字典详情管理 + * + * @author fengshuonan + * @date 2020/10/29 17:43 + */ +public interface DictService extends IService { + + /** + * 新增字典 + * + * @param dictRequest 字典对象 + * @author fengshuonan + * @date 2020/10/29 17:43 + */ + void addDict(DictRequest dictRequest); + + /** + * 修改字典 + * + * @param dictRequest 字典对象 + * @author fengshuonan + * @date 2020/10/29 17:43 + */ + void updateDict(DictRequest dictRequest); + + /** + * 更新字典状态 + * + * @param dictRequest 字典对象 + * @author fengshuonan + * @date 2020/10/29 18:47 + */ + void updateDictStatus(DictRequest dictRequest); + + /** + * 删除字典 + * + * @param dictRequest 字典对象 + * @author fengshuonan + * @date 2020/10/29 17:43 + */ + void deleteDict(DictRequest dictRequest); + + /** + * 查询字典详情 + * + * @param dictRequest 字典对象 + * @return 字典的详情 + * @author fengshuonan + * @date 2020/10/30 16:15 + */ + Dict findDetail(DictRequest dictRequest); + + /** + * 获取字典列表 + * + * @param dictRequest 字典对象 + * @return 字典列表 + * @author fengshuonan + * @date 2020/10/29 18:48 + */ + List findList(DictRequest dictRequest); + + /** + * 获取字典列表(带分页) + * + * @param dictRequest 查询条件 + * @return 带分页的列表 + * @author fengshuonan + * @date 2020/10/29 18:48 + */ + PageResult findPageList(DictRequest dictRequest); + + /** + * 获取树形字典列表 + * + * @param dictRequest 查询条件 + * @return 字典信息列表 + * @author fengshuonan + * @date 2020/10/29 18:50 + */ + List getTreeDictList(DictRequest dictRequest); + + /** + * 字典的code校验重复 + * + * @param dictRequest 查询条件 + * @return true-code没有重复,false-code存在重复 + * @author fengshuonan + * @date 2020/10/29 18:50 + */ + boolean validateCodeAvailable(DictRequest dictRequest); + +} diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/DictTypeService.java b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/DictTypeService.java new file mode 100644 index 000000000..0bc328d5a --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/DictTypeService.java @@ -0,0 +1,87 @@ +package cn.stylefeng.roses.kernel.dict.modular.service; + + +import cn.stylefeng.roses.kernel.dict.modular.entity.DictType; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.dict.modular.pojo.request.DictTypeRequest; + +import java.util.List; + +/** + * 字典类型管理 + * + * @author fengshuonan + * @date 2020/10/29 18:54 + */ +public interface DictTypeService extends IService { + + /** + * 添加字典类型 + * + * @param dictTypeRequest 字典类型请求 + * @author fengshuonan + * @date 2020/10/29 18:55 + */ + void addDictType(DictTypeRequest dictTypeRequest); + + /** + * 修改字典类型 + * + * @param dictTypeRequest 字典类型请求 + * @author fengshuonan + * @date 2020/10/29 18:55 + */ + void updateDictType(DictTypeRequest dictTypeRequest); + + /** + * 修改字典状态 + * + * @param dictTypeRequest 字典类型请求 + * @author fengshuonan + * @date 2020/10/29 18:56 + */ + void updateDictTypeStatus(DictTypeRequest dictTypeRequest); + + /** + * 删除字典类型 + * + * @param dictTypeRequest 字典类型请求 + * @author fengshuonan + * @date 2020/10/29 18:55 + */ + void deleteDictType(DictTypeRequest dictTypeRequest); + + /** + * 获取字典类型列表 + * + * @param dictTypeRequest 字典类型请求 + * @return 字典类型列表 + * @author fengshuonan + * @date 2020/10/29 18:55 + */ + List getDictTypeList(DictTypeRequest dictTypeRequest); + + /** + * 获取字典类型列表(带分页) + * + * @param page 分页参数 + * @param dictTypeRequest 字典类型请求 + * @return 字典类型列表 + * @author fengshuonan + * @date 2020/10/29 18:55 + */ + PageResult getDictTypePageList(Page page, DictTypeRequest dictTypeRequest); + + /** + * code校验重复 + * + * @param dictTypeRequest 字典类型请求 + * @return true-没有重复,false-有重复的 + * @author fengshuonan + * @date 2020/10/29 18:56 + */ + boolean validateCodeAvailable(DictTypeRequest dictTypeRequest); + +} diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/impl/DictServiceImpl.java b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/impl/DictServiceImpl.java new file mode 100644 index 000000000..154ed4e14 --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/impl/DictServiceImpl.java @@ -0,0 +1,243 @@ +package cn.stylefeng.roses.kernel.dict.modular.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.dict.modular.mapper.DictMapper; +import cn.stylefeng.roses.kernel.dict.modular.pojo.TreeDictInfo; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.dict.api.exception.DictException; +import cn.stylefeng.roses.kernel.dict.api.exception.enums.DictExceptionEnum; +import cn.stylefeng.roses.kernel.dict.modular.entity.Dict; +import cn.stylefeng.roses.kernel.dict.modular.pojo.request.DictRequest; +import cn.stylefeng.roses.kernel.dict.modular.service.DictService; +import cn.stylefeng.roses.kernel.rule.enums.StatusEnum; +import cn.stylefeng.roses.kernel.rule.factory.DefaultTreeBuildFactory; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +import static cn.stylefeng.roses.kernel.dict.api.constants.DictConstants.DEFAULT_DICT_PARENT_ID; +import static cn.stylefeng.roses.kernel.dict.api.exception.enums.DictExceptionEnum.WRONG_DICT_STATUS; + +/** + * 基础字典 服务实现类 + * + * @author majianguo + * @version 1.0 + * @date 2020/10/28 上午9:48 + */ +@Service +public class DictServiceImpl extends ServiceImpl implements DictService { + + @Override + public void addDict(DictRequest dictRequest) { + + // 判断某个字典类型下,该编码是否重复 + LambdaQueryWrapper codeRepeatWrapper = new LambdaQueryWrapper<>(); + codeRepeatWrapper + .eq(Dict::getDictTypeCode, dictRequest.getDictTypeCode()) + .and(i -> i.eq(Dict::getDictCode, dictRequest.getDictTypeCode())); + + // 如果重复,抛出异常 + int codeCount = this.baseMapper.selectCount(codeRepeatWrapper); + if (codeCount > 0) { + String userTip = StrUtil.format(DictExceptionEnum.DICT_CODE_REPEAT.getUserTip(), dictRequest.getDictTypeCode(), dictRequest.getDictCode()); + throw new DictException(DictExceptionEnum.DICT_CODE_REPEAT, userTip); + } + + // 判断某个字典类型下,字典的名称是否重复 + LambdaQueryWrapper nameRepeatWrapper = new LambdaQueryWrapper<>(); + nameRepeatWrapper + .eq(Dict::getDictTypeCode, dictRequest.getDictTypeCode()) + .and(i -> i.eq(Dict::getDictName, dictRequest.getDictName())); + + // 如果重复,抛出异常 + int nameCount = this.baseMapper.selectCount(nameRepeatWrapper); + if (nameCount > 0) { + String userTip = StrUtil.format(DictExceptionEnum.DICT_NAME_REPEAT.getUserTip(), dictRequest.getDictTypeCode(), dictRequest.getDictName()); + throw new DictException(DictExceptionEnum.DICT_NAME_REPEAT, userTip); + } + + // 如果父节点为空,则填充为默认的父节点id + if (dictRequest.getParentDictId() == null) { + dictRequest.setParentDictId(DEFAULT_DICT_PARENT_ID); + } + + // 如果父节点不为空,并且不是-1,则判断父节点存不存在,防止脏数据 + else { + if (!DEFAULT_DICT_PARENT_ID.equals(dictRequest.getParentDictId())) { + Dict parentDict = this.getById(dictRequest.getParentDictId()); + if (parentDict == null) { + String userTip = StrUtil.format(DictExceptionEnum.PARENT_DICT_NOT_EXISTED.getUserTip(), dictRequest.getParentDictId()); + throw new DictException(DictExceptionEnum.PARENT_DICT_NOT_EXISTED, userTip); + } + } + } + + // dto转化为实体 + Dict dict = new Dict(); + BeanUtil.copyProperties(dictRequest, new Dict()); + + // dictId是自动生成 + dict.setId(null); + + // 设置状态启用 + dict.setDictStatus(StatusEnum.ENABLE.getCode()); + + this.baseMapper.insert(dict); + } + + @Override + public void updateDict(DictRequest dictRequest) { + + // 查询字典是否存在 + Dict oldDict = this.getById(dictRequest.getId()); + if (oldDict == null) { + String userTip = StrUtil.format(DictExceptionEnum.DICT_NOT_EXISTED.getUserTip(), dictRequest.getId()); + throw new DictException(DictExceptionEnum.DICT_NOT_EXISTED, userTip); + } + + // 判断某个字典类型下,该编码是否重复 + LambdaQueryWrapper codeRepeatWrapper = new LambdaQueryWrapper<>(); + codeRepeatWrapper + .eq(Dict::getDictTypeCode, dictRequest.getDictTypeCode()) + .and(i -> i.eq(Dict::getDictCode, dictRequest.getDictTypeCode())) + .and(i -> i.ne(Dict::getId, dictRequest.getId())); + + // 如果重复,抛出异常 + int codeCount = this.baseMapper.selectCount(codeRepeatWrapper); + if (codeCount > 0) { + String userTip = StrUtil.format(DictExceptionEnum.DICT_CODE_REPEAT.getUserTip(), dictRequest.getDictTypeCode(), dictRequest.getDictCode()); + throw new DictException(DictExceptionEnum.DICT_CODE_REPEAT, userTip); + } + + // 判断某个字典类型下,字典的名称是否重复 + LambdaQueryWrapper nameRepeatWrapper = new LambdaQueryWrapper<>(); + nameRepeatWrapper + .eq(Dict::getDictTypeCode, dictRequest.getDictTypeCode()) + .and(i -> i.eq(Dict::getDictName, dictRequest.getDictName())) + .and(i -> i.ne(Dict::getId, dictRequest.getId())); + + // 如果重复,抛出异常 + int nameCount = this.baseMapper.selectCount(nameRepeatWrapper); + if (nameCount > 0) { + String userTip = StrUtil.format(DictExceptionEnum.DICT_NAME_REPEAT.getUserTip(), dictRequest.getDictTypeCode(), dictRequest.getDictName()); + throw new DictException(DictExceptionEnum.DICT_NAME_REPEAT, userTip); + } + + // 不能修改字典类型和编码 + dictRequest.setDictTypeCode(null); + dictRequest.setDictCode(null); + + // model转化为entity + BeanUtil.copyProperties(dictRequest, oldDict, CopyOptions.create().setIgnoreNullValue(true)); + + this.updateById(oldDict); + } + + @Override + public void updateDictStatus(DictRequest dictRequest) { + + // 检查参数状态是否合法 + if (StatusEnum.codeToEnum(dictRequest.getDictStatus()) == null) { + String userTip = StrUtil.format(WRONG_DICT_STATUS.getUserTip(), dictRequest.getDictStatus()); + throw new DictException(WRONG_DICT_STATUS, userTip); + } + + // 查询对应的字典信息 + Dict dict = this.baseMapper.selectById(dictRequest.getId()); + if (dict == null) { + String userTip = StrUtil.format(DictExceptionEnum.DICT_NOT_EXISTED.getUserTip(), dictRequest.getId()); + throw new DictException(DictExceptionEnum.DICT_NOT_EXISTED, userTip); + } + + // 修改状态 + dict.setDictStatus(dictRequest.getDictStatus()); + this.updateById(dict); + } + + @Override + public void deleteDict(DictRequest dictRequest) { + this.baseMapper.deleteById(dictRequest.getId()); + } + + @Override + public Dict findDetail(DictRequest dictRequest) { + return this.baseMapper.findDetail(dictRequest); + } + + @Override + public List findList(DictRequest dictRequest) { + if (dictRequest == null) { + dictRequest = new DictRequest(); + } + + return baseMapper.findList(null, dictRequest); + } + + @Override + public PageResult findPageList(DictRequest dictRequest) { + if (dictRequest == null) { + dictRequest = new DictRequest(); + } + + Page page = PageFactory.defaultPage(); + baseMapper.findList(page, dictRequest); + + return PageResultFactory.createPageResult(page); + } + + @Override + public List getTreeDictList(DictRequest dictRequest) { + + // 获取字典类型下所有的字典 + List dictList = this.findList(dictRequest); + if (dictList == null || dictList.isEmpty()) { + return new ArrayList<>(); + } + + // 构造树节点信息 + ArrayList treeDictInfos = new ArrayList<>(); + for (Dict dict : dictList) { + TreeDictInfo treeDictInfo = new TreeDictInfo(); + treeDictInfo.setDictId(dict.getId()); + treeDictInfo.setDictCode(dict.getDictCode()); + treeDictInfo.setParentDictId(dict.getParentDictId()); + treeDictInfo.setDictName(dict.getDictName()); + treeDictInfos.add(treeDictInfo); + } + + // 构建菜单树 + return new DefaultTreeBuildFactory().doTreeBuild(treeDictInfos); + } + + @Override + public boolean validateCodeAvailable(DictRequest dictRequest) { + + // 判断编码是否重复 + LambdaQueryWrapper codeRepeatWrapper = new LambdaQueryWrapper<>(); + codeRepeatWrapper + .eq(Dict::getDictTypeCode, dictRequest.getDictTypeCode()) + .and(i -> i.eq(Dict::getDictCode, dictRequest.getDictCode())); + + // 如果传了字典id代表是编辑字典 + if (ObjectUtil.isNotEmpty(dictRequest.getId())) { + codeRepeatWrapper.and(i -> i.ne(Dict::getId, dictRequest.getId())); + } + + // 如果重复,抛出异常 + int codeCount = this.baseMapper.selectCount(codeRepeatWrapper); + + // 如果是小于等于0,则这个编码可以用 + return codeCount <= 0; + } + +} diff --git a/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/impl/DictTypeServiceImpl.java b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/impl/DictTypeServiceImpl.java new file mode 100644 index 000000000..28f0b7869 --- /dev/null +++ b/kernel-s-dict/dict-business/src/main/java/cn/stylefeng/roses/kernel/dict/modular/service/impl/DictTypeServiceImpl.java @@ -0,0 +1,151 @@ +package cn.stylefeng.roses.kernel.dict.modular.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.dict.modular.entity.DictType; +import cn.stylefeng.roses.kernel.dict.modular.mapper.DictTypeMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.dict.api.exception.DictException; +import cn.stylefeng.roses.kernel.dict.api.exception.enums.DictExceptionEnum; +import cn.stylefeng.roses.kernel.dict.modular.pojo.request.DictTypeRequest; +import cn.stylefeng.roses.kernel.dict.modular.service.DictTypeService; +import cn.stylefeng.roses.kernel.rule.enums.StatusEnum; +import org.springframework.stereotype.Service; + +import java.util.List; + + +/** + * 字典类型表 服务实现类 + * + * @author majianguo + * @version 1.0 + * @date 2020/10/28 上午9:58 + */ +@Service +public class DictTypeServiceImpl extends ServiceImpl implements DictTypeService { + + @Override + public void addDictType(DictTypeRequest dictTypeRequest) { + + //判断有没有相同编码的字典 + LambdaQueryWrapper codeWrapper = new LambdaQueryWrapper<>(); + codeWrapper.eq(DictType::getDictTypeCode, dictTypeRequest.getDictTypeCode()); + + int dictTypes = this.baseMapper.selectCount(codeWrapper); + if (dictTypes > 0) { + String userTip = StrUtil.format(DictExceptionEnum.DICT_TYPE_CODE_REPEAT.getUserTip(), dictTypeRequest.getDictTypeCode()); + throw new DictException(DictExceptionEnum.DICT_TYPE_CODE_REPEAT, userTip); + } + + // 模型转化 + DictType dictType = new DictType(); + BeanUtil.copyProperties(dictTypeRequest, dictType); + + // 设置初始状态 + dictType.setDictTypeStatus(StatusEnum.ENABLE.getCode()); + this.baseMapper.insert(dictType); + } + + @Override + public void updateDictType(DictTypeRequest dictTypeRequest) { + + // 获取字典类型是否存在 + DictType oldDictType = this.baseMapper.selectById(dictTypeRequest.getId()); + if (oldDictType == null) { + String userTip = StrUtil.format(DictExceptionEnum.DICT_TYPE_NOT_EXISTED.getUserTip(), dictTypeRequest.getId()); + throw new DictException(DictExceptionEnum.DICT_TYPE_NOT_EXISTED, userTip); + } + + // 判断有没有相同编码的字典 + LambdaQueryWrapper codeWrapper = new LambdaQueryWrapper<>(); + codeWrapper + .eq(DictType::getDictTypeCode, dictTypeRequest.getDictTypeCode()) + .and(i -> i.ne(DictType::getId, dictTypeRequest.getId())); + + int dictTypes = this.baseMapper.selectCount(codeWrapper); + if (dictTypes > 0) { + String userTip = StrUtil.format(DictExceptionEnum.DICT_TYPE_CODE_REPEAT.getUserTip(), dictTypeRequest.getDictTypeCode()); + throw new DictException(DictExceptionEnum.DICT_TYPE_CODE_REPEAT, userTip); + } + + // 模型转化 + BeanUtil.copyProperties(dictTypeRequest, oldDictType); + + // 不能修改字典编码 + oldDictType.setDictTypeCode(null); + + this.updateById(oldDictType); + } + + @Override + public void updateDictTypeStatus(DictTypeRequest dictTypeRequest) { + + // 获取字典类型是否存在 + DictType oldDictType = this.baseMapper.selectById(dictTypeRequest.getId()); + if (oldDictType == null) { + String userTip = StrUtil.format(DictExceptionEnum.DICT_TYPE_NOT_EXISTED.getUserTip(), dictTypeRequest.getId()); + throw new DictException(DictExceptionEnum.DICT_TYPE_NOT_EXISTED, userTip); + } + + // 判断状态是否正确 + StatusEnum statusEnum = StatusEnum.codeToEnum(dictTypeRequest.getDictTypeStatus()); + if (statusEnum == null) { + String userTip = StrUtil.format(DictExceptionEnum.WRONG_DICT_STATUS.getUserTip(), dictTypeRequest.getDictTypeStatus()); + throw new DictException(DictExceptionEnum.WRONG_DICT_STATUS, userTip); + } + + // 修改状态 + oldDictType.setDictTypeStatus(dictTypeRequest.getDictTypeStatus()); + + this.updateById(oldDictType); + } + + @Override + public void deleteDictType(DictTypeRequest dictTypeRequest) { + this.baseMapper.deleteById(dictTypeRequest.getId()); + } + + @Override + public List getDictTypeList(DictTypeRequest dictTypeRequest) { + return this.baseMapper.findList(null, dictTypeRequest); + } + + @Override + public PageResult getDictTypePageList(Page page, DictTypeRequest dictTypeRequest) { + if (page == null) { + page = PageFactory.defaultPage(); + } + + if (dictTypeRequest == null) { + dictTypeRequest = new DictTypeRequest(); + } + + this.baseMapper.findList(page, dictTypeRequest); + + return PageResultFactory.createPageResult(page); + } + + @Override + public boolean validateCodeAvailable(DictTypeRequest dictTypeRequest) { + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + wrapper.eq(DictType::getDictTypeCode, dictTypeRequest.getDictTypeCode()); + + if (ObjectUtil.isNotEmpty(dictTypeRequest.getId())) { + wrapper.and(i -> i.ne(DictType::getId, dictTypeRequest.getId())); + } + + Integer selectCount = this.baseMapper.selectCount(wrapper); + + return selectCount <= 0; + } + +} diff --git a/kernel-s-dict/dict-spring-boot-starter/README.md b/kernel-s-dict/dict-spring-boot-starter/README.md new file mode 100644 index 000000000..5a30f1a55 --- /dev/null +++ b/kernel-s-dict/dict-spring-boot-starter/README.md @@ -0,0 +1 @@ +字典业务的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-s-dict/dict-spring-boot-starter/pom.xml b/kernel-s-dict/dict-spring-boot-starter/pom.xml new file mode 100644 index 000000000..2cca67dea --- /dev/null +++ b/kernel-s-dict/dict-spring-boot-starter/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-dict + 1.0.0 + ../pom.xml + + + dict-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + dict-business + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/kernel-s-dict/dict-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/dict/starter/GunsDictAutoConfiguration.java b/kernel-s-dict/dict-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/dict/starter/GunsDictAutoConfiguration.java new file mode 100644 index 000000000..1497521f1 --- /dev/null +++ b/kernel-s-dict/dict-spring-boot-starter/src/main/java/cn/stylefeng/roses/kernel/dict/starter/GunsDictAutoConfiguration.java @@ -0,0 +1,14 @@ +package cn.stylefeng.roses.kernel.dict.starter; + +import org.springframework.context.annotation.Configuration; + +/** + * 字典业务的自动配置 + * + * @author fengshuonan + * @date 2020/12/1 21:54 + */ +@Configuration +public class GunsDictAutoConfiguration { + +} diff --git a/kernel-s-dict/dict-spring-boot-starter/src/main/resources/META-INF/spring.factories b/kernel-s-dict/dict-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..56ee4fae0 --- /dev/null +++ b/kernel-s-dict/dict-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.stylefeng.roses.kernel.dict.starter.GunsDictAutoConfiguration \ No newline at end of file diff --git a/kernel-s-dict/pom.xml b/kernel-s-dict/pom.xml new file mode 100644 index 000000000..e52fbcba2 --- /dev/null +++ b/kernel-s-dict/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-s-dict + + pom + + + dict-api + dict-business + dict-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-s-system/README.md b/kernel-s-system/README.md new file mode 100644 index 000000000..2c58c1feb --- /dev/null +++ b/kernel-s-system/README.md @@ -0,0 +1,7 @@ +系统管理模块,包含用户,角色,权限,菜单等业务的维护 + +每个业务模块拆分成一个system-business-xxx小的模块 + +各个模块之间的交互,通过system-api创建相关的接口 + +这个模块,用在最基础的业务上,不用在企业级的架构上的业务,企业级架构业务要比这复杂 \ No newline at end of file diff --git a/kernel-s-system/_sql/system.sql b/kernel-s-system/_sql/system.sql new file mode 100644 index 000000000..131281b2d --- /dev/null +++ b/kernel-s-system/_sql/system.sql @@ -0,0 +1,431 @@ +/* + Navicat Premium Data Transfer + + Source Server : localhost + Source Server Type : MySQL + Source Server Version : 50732 + Source Host : localhost:3306 + Source Schema : guns + + Target Server Type : MySQL + Target Server Version : 50732 + File Encoding : 65001 + + Date: 03/12/2020 17:37:58 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for sys_app +-- ---------------------------- +DROP TABLE IF EXISTS `sys_app`; +CREATE TABLE `sys_app` ( + `id` bigint(20) NOT NULL COMMENT '主键id', + `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '应用名称', + `code` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编码', + `active_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否默认激活(Y-是,N-否)', + `status_flag` tinyint(4) NOT NULL COMMENT '状态(字典 1启用 2禁用)', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否删除(Y-已删除,N-未删除)', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '修改人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统应用表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_app +-- ---------------------------- +INSERT INTO `sys_app` VALUES (1265476890672672821, '系统应用', 'system', 'Y', 1, 'N', '2020-03-25 19:07:00', 1265476890672672808, '2020-09-17 09:11:21', 1265476890672672808); +INSERT INTO `sys_app` VALUES (1265476890672672822, '业务应用', 'business', 'N', 1, 'N', '2020-03-26 08:40:33', 1265476890672672808, '2020-09-17 09:11:21', 1265476890672672808); + +-- ---------------------------- +-- Table structure for sys_config +-- ---------------------------- +DROP TABLE IF EXISTS `sys_config`; +CREATE TABLE `sys_config` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '名称', + `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '编码', + `value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '属性值', + `sys_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否是系统参数(Y-是,N-否)', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `status` int(2) NULL DEFAULT NULL COMMENT '状态(字典 1正常 2停用)', + `group_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '常量所属分类的编码,来自于“常量的分类”字典', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否删除,Y-被删除,N-未删除', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '修改人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '参数配置' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_database_info +-- ---------------------------- +DROP TABLE IF EXISTS `sys_database_info`; +CREATE TABLE `sys_database_info` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `db_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '数据库名称(英文名称)', + `jdbc_driver` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'jdbc的驱动类型', + `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '数据库连接的账号', + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '数据库连接密码', + `jdbc_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'jdbc的url', + `remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注,摘要', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否删除,Y-被删除,N-未删除', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '修改人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '数据库信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_dict +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict`; +CREATE TABLE `sys_dict` ( + `id` bigint(20) NOT NULL COMMENT '字典id', + `dict_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '字典编码', + `dict_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '字典名称', + `dict_type_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '字典类型的编码', + `dict_short_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '字典简称', + `dict_short_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '字典简称的编码', + `parent_dict_id` bigint(20) NULL DEFAULT NULL COMMENT '上级字典的id(如果没有上级字典id,则为-1)', + `dict_status` int(2) NULL DEFAULT NULL COMMENT '状态(1:启用,2:禁用),参考 StatusEnum', + `dict_sort` double(11, 5) NULL DEFAULT NULL COMMENT '排序,带小数点', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否删除,Y-被删除,N-未删除', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '修改人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典实体' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_dict_type +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_type`; +CREATE TABLE `sys_dict_type` ( + `id` bigint(20) NOT NULL COMMENT '字典类型id', + `dict_type_class` int(2) NULL DEFAULT NULL COMMENT '字典类型: 1-业务类型,2-系统类型,参考 DictTypeClassEnum', + `dict_type_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '字典类型编码', + `dict_type_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '字典类型名称', + `dict_type_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '字典类型描述', + `dict_type_status` int(2) NULL DEFAULT NULL COMMENT '字典类型的状态:1-启用,2-禁用,参考 StatusEnum', + `dict_type_sort` double(11, 5) NULL DEFAULT NULL COMMENT '排序,带小数点', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否删除,Y-被删除,N-未删除', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '修改人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表,一个字典类型下有多个字典' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_employee +-- ---------------------------- +DROP TABLE IF EXISTS `sys_employee`; +CREATE TABLE `sys_employee` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `user_id` bigint(20) NOT NULL COMMENT '用户id', + `organization_id` bigint(20) NOT NULL COMMENT '所属机构id', + `position_ids` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '职位id集合,用逗号隔开', + `main_dept_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否是主要部门,Y-是,N-否,一个人只能有一个主要部门', + `employee_no` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '员工编号', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '添加时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '添加人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '企业员工表,用户-组织机构的关联' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_file_info +-- ---------------------------- +DROP TABLE IF EXISTS `sys_file_info`; +CREATE TABLE `sys_file_info` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `file_location` int(2) NULL DEFAULT NULL COMMENT '文件存储位置(1:阿里云,2:腾讯云,3:minio,4:本地)', + `file_bucket` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件仓库', + `file_origin_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件名称(上传时候的文件名)', + `file_suffix` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件后缀', + `file_size_kb` bigint(20) NULL DEFAULT NULL COMMENT '文件大小kb', + `file_size_info` varchar(0) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件大小信息,计算后的', + `file_object_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '存储到bucket的名称(文件唯一标识id)', + `file_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '存储路径', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否删除,Y-被删除,N-未删除', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '修改人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_log`; +CREATE TABLE `sys_log` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '日志的名称,一般为业务名称', + `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '日志记录的内容', + `app_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '服务名称,一般为spring.application.name', + `request_params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'http或方法的请求参数体', + `request_result` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'http或方法的请求结果', + `date_time` datetime(0) NULL DEFAULT NULL COMMENT '操作发生的时间', + `server_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '当前服务器的ip', + `token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '客户端请求的token,如果是http请求,并且用户已经登录', + `user_id` bigint(20) NULL DEFAULT NULL COMMENT '客户端请求的用户id,如果是http请求,并且用户已经登录', + `client_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '客户端的ip', + `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '当前用户请求的url', + `http_method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '请求方式(GET POST PUT DELETE)', + `browser` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '浏览器,如果是http请求', + `os` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '操作系统,如果是http请求', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否删除,Y-被删除,N-未删除', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '修改人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '日志记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menu`; +CREATE TABLE `sys_menu` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `pid` bigint(20) NOT NULL COMMENT '父id,顶级节点的父id是0', + `pids` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '父id集合,中括号包住,逗号分隔', + `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '名称', + `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜单的编码', + `app_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '应用分类(应用编码)', + `visible` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否可见(Y-是,N-否)', + `sort` decimal(11, 2) NOT NULL COMMENT '排序', + `status_flag` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态(1-启用,2-禁用)', + `resource_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '关联的资源的编码', + `icon` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图标', + `router` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '路由地址,浏览器显示的URL,例如/menu', + `component` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '前端组件名', + `link_open_type` tinyint(4) NULL DEFAULT NULL COMMENT '外部链接打开方式(1内置外链 2新页面外链)', + `link_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '外部链接地址', + `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否删除,Y-被删除,N-未删除', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '修改人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统菜单表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_organization +-- ---------------------------- +DROP TABLE IF EXISTS `sys_organization`; +CREATE TABLE `sys_organization` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `pid` bigint(20) NOT NULL COMMENT '父id,顶级节点父id是0', + `pids` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '父ids', + `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '组织名称', + `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '组织编码', + `sort` decimal(11, 2) NOT NULL COMMENT '排序', + `status_flag` tinyint(4) NOT NULL COMMENT '状态(1-启用,2-禁用)', + `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '删除标记(Y-已删除,N-未删除)', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统组织机构表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_organization +-- ---------------------------- +INSERT INTO `sys_organization` VALUES (1, 0, '[0],', 'Guns总公司', 'guns_level_one', 1.00, 1, NULL, 'N', NULL, NULL, NULL, NULL); +INSERT INTO `sys_organization` VALUES (2, 1, '[0],[1],', '北京分公司', 'guns_beijing', 2.00, 1, NULL, 'N', NULL, NULL, NULL, NULL); +INSERT INTO `sys_organization` VALUES (3, 2, '[0],[1],[2],', '北京东直门分公司', 'guns_beijing_dongzhimen', 3.00, 1, NULL, 'N', NULL, NULL, NULL, NULL); + +-- ---------------------------- +-- Table structure for sys_position +-- ---------------------------- +DROP TABLE IF EXISTS `sys_position`; +CREATE TABLE `sys_position` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '职位名称', + `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '职位编码', + `sort` decimal(11, 2) NOT NULL COMMENT '排序', + `status_flag` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态(1-启用,2-禁用)', + `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '删除标记(Y-已删除,N-未删除)', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `CODE_UNI`(`code`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统职位表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_resource +-- ---------------------------- +DROP TABLE IF EXISTS `sys_resource`; +CREATE TABLE `sys_resource` ( + `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '资源id', + `app_code` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '应用编码', + `code` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '资源编码', + `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '资源名称', + `project_code` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '项目编码', + `class_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '类名称', + `method_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '方法名称', + `modular_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '资源模块编码', + `modular_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '资源模块名称', + `ip_address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '资源初始化的服务器ip地址', + `url` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '资源url', + `http_method` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'http请求方法', + `menu_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否是方法(Y-页面,N-API接口)', + `required_login_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否需要登录(Y-是,N-否)', + `required_permission_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '是否需要鉴权(Y-是,N-否)', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '更新人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '资源表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role`; +CREATE TABLE `sys_role` ( + `id` bigint(20) NOT NULL COMMENT '主键id', + `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名称', + `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色编码', + `sort` decimal(11, 2) NOT NULL COMMENT '序号', + `data_scope_type` tinyint(4) NOT NULL DEFAULT 1 COMMENT '数据范围类型(字典 10全部数据 20本部门及以下数据 30本部门数据 40仅本人数据 50自定义数据)', + `status_flag` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态(1-启用,2-禁用)', + `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否删除(Y-已删除,N-未删除)', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统角色表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_role_data_scope +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_data_scope`; +CREATE TABLE `sys_role_data_scope` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `role_id` bigint(20) NOT NULL COMMENT '角色id', + `organization_id` bigint(20) NOT NULL COMMENT '机构id', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统角色数据范围表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_role_resource +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_resource`; +CREATE TABLE `sys_role_resource` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `role_id` bigint(20) NOT NULL COMMENT '角色id', + `resource_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '资源id', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统角色菜单表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_sms +-- ---------------------------- +DROP TABLE IF EXISTS `sys_sms`; +CREATE TABLE `sys_sms` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `phone_numbers` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '手机号', + `validate_code` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '短信验证码', + `template_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '短信模板ID', + `biz_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '回执id', + `status` int(2) NULL DEFAULT NULL COMMENT '发送状态(字典 0 未发送,1 发送成功,2 发送失败,3 失效)', + `source` int(5) NULL DEFAULT NULL COMMENT '来源(字典 1 app, 2 pc, 3 其他)', + `invalid_time` datetime(0) NULL DEFAULT NULL COMMENT '失效时间', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否删除,Y-被删除,N-未删除', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '修改人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统短信表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_timers +-- ---------------------------- +DROP TABLE IF EXISTS `sys_timers`; +CREATE TABLE `sys_timers` ( + `id` bigint(20) NOT NULL COMMENT '定时器id', + `timer_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '任务名称', + `action_class` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '执行任务的class的类名(实现了TimerTaskRunner接口的类的全称)', + `cron` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '定时任务表达式', + `job_status` int(2) NULL DEFAULT NULL COMMENT '状态(字典 1运行 2停止)', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注信息', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '是否删除,Y-被删除,N-未删除', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '修改人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `account` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '账号', + `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码,加密方式为BCrypt', + `nick_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称', + `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '姓名', + `avatar` bigint(20) NULL DEFAULT NULL COMMENT '头像,存的为文件id', + `birthday` date NULL DEFAULT NULL COMMENT '生日', + `sex` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '性别(M-男,F-女)', + `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱', + `phone` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机', + `tel` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '电话', + `last_login_ip` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最后登陆IP', + `last_login_time` datetime(0) NULL DEFAULT NULL COMMENT '最后登陆时间', + `super_admin_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0' COMMENT '是否是超级管理员,超级管理员可以拥有所有权限(Y-是,N-否)', + `status_flag` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态(字典 1正常 2冻结)', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '删除标记(Y-已删除,N-未删除)', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `update_user` bigint(20) NULL DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统用户表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_user_data_scope +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_data_scope`; +CREATE TABLE `sys_user_data_scope` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `user_id` bigint(20) NOT NULL COMMENT '用户id', + `organization_id` bigint(20) NOT NULL COMMENT '机构id', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统用户数据范围表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for sys_user_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_role`; +CREATE TABLE `sys_user_role` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `user_id` bigint(20) NOT NULL COMMENT '用户id', + `role_id` bigint(20) NOT NULL COMMENT '角色id', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统用户角色表' ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/kernel-s-system/pom.xml b/kernel-s-system/pom.xml new file mode 100644 index 000000000..d47291dbc --- /dev/null +++ b/kernel-s-system/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + + cn.stylefeng.roses + roses-kernel + 1.0.0 + ../pom.xml + + + kernel-s-system + + pom + + + system-api + system-business-app + system-business-organization + system-business-resource + system-business-user + system-business-role + system-business-menu + system-spring-boot-starter + + + + + + + cn.stylefeng.roses + kernel-a-rule + 1.0.0 + + + + + diff --git a/kernel-s-system/system-api/README.md b/kernel-s-system/system-api/README.md new file mode 100644 index 000000000..07b9a749e --- /dev/null +++ b/kernel-s-system/system-api/README.md @@ -0,0 +1 @@ +demo模块的api \ No newline at end of file diff --git a/kernel-s-system/system-api/pom.xml b/kernel-s-system/system-api/pom.xml new file mode 100644 index 000000000..c7d0d36f6 --- /dev/null +++ b/kernel-s-system/system-api/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-system + 1.0.0 + ../pom.xml + + + system-api + + jar + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + cn.stylefeng.roses + config-api + 1.0.0 + + + + + + cn.stylefeng.roses + auth-api + 1.0.0 + + + + + + javax.servlet + javax.servlet-api + provided + + + org.springframework + spring-web + provided + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/AppServiceApi.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/AppServiceApi.java new file mode 100644 index 000000000..b5661d1d2 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/AppServiceApi.java @@ -0,0 +1,25 @@ +package cn.stylefeng.roses.kernel.system; + +import cn.stylefeng.roses.kernel.rule.pojo.dict.SimpleDict; + +import java.util.Set; + +/** + * 应用相关api + * + * @author fengshuonan + * @date 2020/11/24 21:37 + */ +public interface AppServiceApi { + + /** + * 通过app编码获取app的详情 + * + * @param appCodes 应用的编码 + * @return 应用的信息 + * @author fengshuonan + * @date 2020/11/29 20:06 + */ + Set getAppsByAppCodes(Set appCodes); + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/DataScopeApi.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/DataScopeApi.java new file mode 100644 index 000000000..3de2c3196 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/DataScopeApi.java @@ -0,0 +1,25 @@ +package cn.stylefeng.roses.kernel.system; + +import cn.stylefeng.roses.kernel.system.pojo.organization.DataScopeResponse; + +/** + * 数据范围的获取接口 + * + * @author fengshuonan + * @date 2020/11/6 11:54 + */ +public interface DataScopeApi { + + /** + * 获取用户的数据范围 + *

+ * 目前不考虑一个用户多角色多部门下的数据范围,只考虑一个用户只对应一个主部门下的数据范围 + * + * @param userId 用户id + * @return 数据范围内容 + * @author majianguo + * @date 2020/11/5 上午11:44 + */ + DataScopeResponse getDataScope(Long userId); + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/MenuServiceApi.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/MenuServiceApi.java new file mode 100644 index 000000000..9305ebeb0 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/MenuServiceApi.java @@ -0,0 +1,33 @@ +package cn.stylefeng.roses.kernel.system; + +import java.util.List; + +/** + * 菜单api + * + * @author fengshuonan + * @date 2020/11/24 21:37 + */ +public interface MenuServiceApi { + + /** + * 根据应用编码判断该机构下是否有状态为正常的菜单 + * + * @param appCode 应用编码 + * @return 该应用下是否有正常菜单,true是,false否 + * @author fengshuonan + * @date 2020/11/24 21:37 + */ + boolean hasMenu(String appCode); + + /** + * 通过资源编码集合,获取菜单的id集合 + * + * @param resourceCodes 资源编码集合 + * @return 菜单id的集合 + * @author fengshuonan + * @date 2020/11/26 21:27 + */ + List getMenuIdsByResourceCodes(List resourceCodes); + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/ResourceServiceApi.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/ResourceServiceApi.java new file mode 100644 index 000000000..286743f54 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/ResourceServiceApi.java @@ -0,0 +1,46 @@ +package cn.stylefeng.roses.kernel.system; + +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceDefinition; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceUrlParam; + +import java.util.List; + +/** + * 资源服务相关接口 + * + * @author fengshuonan + * @date 2020/12/3 15:45 + */ +public interface ResourceServiceApi { + + /** + * 通过url获取资源 + * + * @param resourceUrlReq 资源url的封装 + * @return 资源详情 + * @author fengshuonan + * @date 2020/10/19 22:06 + */ + ResourceDefinition getResourceByUrl(ResourceUrlParam resourceUrlReq); + + /** + * 获取资源详情,根据资源id集合 + * + * @param resourceIds 资源id集合 + * @return 资源详情列表 + * @author fengshuonan + * @date 2020/11/29 19:49 + */ + List getResourceListByIds(List resourceIds); + + /** + * 获取资源的url列表,根据资源ids查询 + * + * @param resourceIds 资源id集合 + * @return 资源url列表 + * @author fengshuonan + * @date 2020/11/29 19:49 + */ + List getResourceUrlsListByIds(List resourceIds); + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/RoleServiceApi.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/RoleServiceApi.java new file mode 100644 index 000000000..78ca2e283 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/RoleServiceApi.java @@ -0,0 +1,65 @@ +package cn.stylefeng.roses.kernel.system; + +import cn.stylefeng.roses.kernel.system.pojo.role.response.SysRoleResponse; + +import java.util.List; +import java.util.Set; + +/** + * 角色服务对外部模块的接口 + * + * @author fengshuonan + * @date 2020/11/5 19:17 + */ +public interface RoleServiceApi { + + /** + * 删除角色关联的组织架构数据范围 + * + * @param organizationIds 组织架构id集合 + * @author fengshuonan + * @date 2020/11/5 19:17 + */ + void deleteRoleDataScopeListByOrgIdList(Set organizationIds); + + /** + * 获取角色,通过传递角色id列表 + * + * @param roleIds 角色id列表 + * @return 角色信息列表 + * @author fengshuonan + * @date 2020/11/21 9:17 + */ + List getRolesByIds(List roleIds); + + /** + * 获取角色对应的组织机构范围集合 + * + * @param roleIds 角色id集合 + * @return 组织机构id集合 + * @author fengshuonan + * @date 2020/11/21 9:56 + */ + List getRoleDataScopes(List roleIds); + + /** + * 获取某些角色对应的菜单id集合 + * + * @param roleIds 角色id集合 + * @return 菜单id集合 + * @author fengshuonan + * @date 2020/11/22 23:00 + */ + List getMenuIdsByRoleIds(List roleIds); + + /** + * 获取角色的资源id集合 + * + * @param roleIdList 角色id集合 + * @return 资源id集合 + * @author majianguo + * @date 2020/11/5 上午11:17 + */ + List getRoleResourceList(List roleIdList); + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/SysEmployeeApi.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/SysEmployeeApi.java new file mode 100644 index 000000000..c4b671e75 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/SysEmployeeApi.java @@ -0,0 +1,59 @@ +package cn.stylefeng.roses.kernel.system; + +import cn.stylefeng.roses.kernel.system.pojo.organization.SysEmployeeRequest; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysEmployeeResponse; + +import java.util.List; + +/** + * 企业员工的管理API + *

+ * 企业员工管理就是用户和组织架构的管理 + * + * @author luojie + * @date 2020/11/5 16:23 + */ +public interface SysEmployeeApi { + + /** + * 添加或修改员工信息 + *

+ * 业务步骤都是,先删除该用户的所有员工信息,再添加参数上的员工信息 + * + * @param userId 用户id + * @param sysEmployeeRequest 企业员工信息 + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + void updateEmployee(Long userId, List sysEmployeeRequest); + + /** + * 根据用户id删除员工信息 + * + * @param userId 用户id + * @author luojie + * @date 2020/11/14 14:21 + */ + void deleteEmployeeByUserId(Long userId); + + /** + * 获取用户主任职信息 + * + * @param userId 用户id + * @return 用户的主任职信息 + * @author fengshuonan + * @date 2020/11/19 21:42 + */ + SysEmployeeResponse getUserMainEmployee(Long userId); + + /** + * 获取用户所有的任职信息,包含主任职信息和附属任职信息 + * + * @param userId 用户id + * @return 用户所有的任职信息 + * @author fengshuonan + * @date 2020/11/19 21:43 + */ + List getUserAllEmployee(Long userId); + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/UserServiceApi.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/UserServiceApi.java new file mode 100644 index 000000000..ea878834b --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/UserServiceApi.java @@ -0,0 +1,76 @@ +package cn.stylefeng.roses.kernel.system; + +import cn.stylefeng.roses.kernel.system.pojo.user.UserLoginInfoDTO; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +/** + * 用户管理服务接口 + * + * @author fengshuonan + * @date 2020/10/20 13:20 + */ +public interface UserServiceApi { + + /** + * 获取用户登录需要的详细信息 + * + * @param account 账号 + * @return 用户登录需要的详细信息 + * @author fengshuonan + * @date 2020/10/20 16:59 + */ + UserLoginInfoDTO getUserLoginInfo(String account); + + /** + * 更新用户的登录信息,一般为更新用户的登录时间和ip + * + * @param userId 用户id + * @param date 用户登录时间 + * @param ip 用户登录的ip + * @author fengshuonan + * @date 2020/10/21 14:13 + */ + void updateUserLoginInfo(Long userId, Date date, String ip); + + /** + * 根据机构id集合删除对应的用户-数据范围关联信息 + * + * @param organizationIds 组织架构id集合 + * @author fengshuonan + * @date 2020/10/20 16:59 + */ + void deleteUserDataScopeListByOrgIdList(Set organizationIds); + + /** + * 获取用户的角色id集合 + * + * @param userId 用户id + * @return 角色id集合 + * @author majianguo + * @date 2020/11/5 下午3:57 + */ + List getUserRoleIdList(Long userId); + + /** + * 根据角色id删除对应的用户-角色表关联信息 + * + * @param roleId 角色id + * @author majianguo + * @date 2020/11/5 下午3:58 + */ + void deleteUserRoleListByRoleId(Long roleId); + + /** + * 获取用户单独绑定的数据范围,sys_user_data_scope表中的数据范围 + * + * @param userId 用户id + * @return 用户数据范围 + * @author fengshuonan + * @date 2020/11/21 12:15 + */ + List getUserBindDataScope(Long userId); + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/constants/SymbolConstant.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/constants/SymbolConstant.java new file mode 100644 index 000000000..e0408bf1c --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/constants/SymbolConstant.java @@ -0,0 +1,66 @@ +package cn.stylefeng.roses.kernel.system.constants; + +/** + * 符号常量 + * + * @author majianguo + * @date 2020/11/5 下午2:32 + */ +public interface SymbolConstant { + + String PERIOD = "."; + + String COMMA = ","; + + String COLON = ":"; + + String SEMICOLON = ";"; + + String EXCLAMATION_MARK = "!"; + + String QUESTION_MARK = "?"; + + String HYPHEN = "-"; + + String ASTERISK = "*"; + + String APOSTROPHE = "`"; + + String DASH = "-"; + + String UNDER_SCORE = "_"; + + String SINGLE_QUOTATION_MARK = "'"; + + String DOUBLE_QUOTATION_MARK = "\""; + + String LEFT_ROUND_BRACKETS = "("; + + String RIGHT_ROUND_BRACKETS = ")"; + + String LEFT_SQUARE_BRACKETS = "["; + + String RIGHT_SQUARE_BRACKETS = "]"; + + String LEFT_ANGLE_BRACKETS = "<"; + + String RIGHT_ANGLE_BRACKETS = ">"; + + String LEFT_CURLY_BRACKETS = "{"; + + String RIGHT_CURLY_BRACKETS = "}"; + + String DOLLAR = "$"; + + String PERCENT = "%"; + + String LEFT_DIVIDE = "/"; + + String RIGHT_DIVIDE = "\\"; + + String LEFT_DOUBLE_DIVIDE = "//"; + + String RIGHT_DOUBLE_DIVIDE = "\\\\"; + + String EQUAL = "="; +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/constants/SystemConstants.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/constants/SystemConstants.java new file mode 100644 index 000000000..a0b7bf74d --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/constants/SystemConstants.java @@ -0,0 +1,36 @@ +package cn.stylefeng.roses.kernel.system.constants; + +/** + * 系统管理模块的常量 + * + * @author fengshuonan + * @date 2020/11/4 15:48 + */ +public interface SystemConstants { + + /** + * 系统管理模块的名称 + */ + String SYSTEM_MODULE_NAME = "kernel-s-system"; + + /** + * 异常枚举的步进值 + */ + String SYSTEM_EXCEPTION_STEP_CODE = "18"; + + /** + * 一级节点的父节点id是0 + */ + Long DEFAULT_PARENT_ID = 0L; + + /** + * pids系列字段,每个id的左分隔符 + */ + String PID_LEFT_DIVIDE_SYMBOL = "["; + + /** + * pids系列字段,每个id的右分隔符 + */ + String PID_RIGHT_DIVIDE_SYMBOL = "]"; + +} \ No newline at end of file diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/enums/LinkOpenTypeEnum.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/enums/LinkOpenTypeEnum.java new file mode 100644 index 000000000..fb2786090 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/enums/LinkOpenTypeEnum.java @@ -0,0 +1,33 @@ +package cn.stylefeng.roses.kernel.system.enums; + +import lombok.Getter; + +/** + * 菜单链接打开方式 + * + * @author fengshuonan + * @date 2020/11/23 21:30 + */ +@Getter +public enum LinkOpenTypeEnum { + + /** + * 内部链接(内置iframe页面打开链接) + */ + COMPONENT(1, "内链"), + + /** + * 外部链接方式打开链接,新标签页打开 + */ + INNER(2, "外链"); + + private final Integer code; + + private final String message; + + LinkOpenTypeEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/enums/UserStatusEnum.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/enums/UserStatusEnum.java new file mode 100644 index 000000000..cc0cca02f --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/enums/UserStatusEnum.java @@ -0,0 +1,88 @@ +package cn.stylefeng.roses.kernel.system.enums; + +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.system.exception.SystemModularException; +import cn.stylefeng.roses.kernel.system.exception.enums.SysUserExceptionEnum; +import lombok.Getter; + +/** + * 用户状态的枚举 + * + * @author fengshuonan + * @date 2020/10/20 18:19 + */ +@Getter +public enum UserStatusEnum { + + /** + * 启用 + */ + ENABLE(1, "启用"), + + /** + * 禁用 + */ + DISABLE(2, "禁用"), + + /** + * 冻结 + */ + FREEZE(3, "冻结"); + + private final Integer code; + + private final String message; + + UserStatusEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + /** + * code转化为enum + * + * @author fengshuonan + * @date 2020/10/21 9:29 + */ + public static UserStatusEnum toEnum(Integer code) { + for (UserStatusEnum userStatusEnum : UserStatusEnum.values()) { + if (userStatusEnum.getCode().equals(code)) { + return userStatusEnum; + } + } + return null; + } + + /** + * 获取code对应的message + * + * @author fengshuonan + * @date 2020/10/21 9:29 + */ + public static String getCodeMessage(Integer code) { + UserStatusEnum userStatusEnum = toEnum(code); + if (userStatusEnum != null) { + return userStatusEnum.getMessage(); + } else { + return ""; + } + } + + /** + * 检查请求参数的状态是否正确 + * + * @author stylefeng + * @date 2020/4/30 22:43 + */ + public static void validateUserStatus(Integer code) { + if (code == null) { + throw new SystemModularException(SysUserExceptionEnum.REQUEST_USER_STATUS__EMPTY); + } + if (ENABLE.getCode().equals(code) || DISABLE.getCode().equals(code) || FREEZE.getCode().equals(code)) { + return; + } + String userTip = StrUtil.format(SysUserExceptionEnum.REQUEST_USER_STATUS_ERROR.getUserTip(), code); + throw new SystemModularException(SysUserExceptionEnum.REQUEST_USER_STATUS_ERROR, userTip); + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/DataScopeException.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/DataScopeException.java new file mode 100644 index 000000000..697795669 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/DataScopeException.java @@ -0,0 +1,24 @@ +package cn.stylefeng.roses.kernel.system.exception; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; +import cn.stylefeng.roses.kernel.system.constants.SystemConstants; + +/** + * 数据范围异常,当前用户没有操作该数据的权限 + * + * @author fengshuonan + * @date 2020/11/4 15:50 + */ +public class DataScopeException extends ServiceException { + + public DataScopeException(AbstractExceptionEnum exception,String userTip) { + + super(SystemConstants.SYSTEM_MODULE_NAME, exception.getErrorCode(), userTip); + } + + public DataScopeException(AbstractExceptionEnum exception) { + super(SystemConstants.SYSTEM_MODULE_NAME, exception); + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/SystemModularException.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/SystemModularException.java new file mode 100644 index 000000000..fea8d8e4a --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/SystemModularException.java @@ -0,0 +1,23 @@ +package cn.stylefeng.roses.kernel.system.exception; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; +import cn.stylefeng.roses.kernel.system.constants.SystemConstants; + +/** + * 系统管理模块的异常 + * + * @author fengshuonan + * @date 2020/11/4 15:50 + */ +public class SystemModularException extends ServiceException { + + public SystemModularException(AbstractExceptionEnum exception, String userTip) { + super(SystemConstants.SYSTEM_MODULE_NAME, exception.getErrorCode(), userTip); + } + + public SystemModularException(AbstractExceptionEnum exception) { + super(SystemConstants.SYSTEM_MODULE_NAME, exception); + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/AppExceptionEnum.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/AppExceptionEnum.java new file mode 100644 index 000000000..b0647136d --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/AppExceptionEnum.java @@ -0,0 +1,71 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.system.exception.enums; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import cn.stylefeng.roses.kernel.system.constants.SystemConstants; +import lombok.Getter; + +/** + * 系统应用相关异常枚举 + * + * @author fengshuonan + * @date 2020/3/26 10:11 + */ +@Getter +public enum AppExceptionEnum implements AbstractExceptionEnum { + + /** + * 默认激活的系统只能有一个 + */ + APP_ACTIVE_REPEAT(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "71", "默认激活的系统只能有一个,请检查active参数"), + + /** + * 应用不存在 + */ + APP_NOT_EXIST(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "72", "应用不存在"), + + /** + * 该应用下有菜单 + */ + APP_CANNOT_DELETE(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "75", "该应用下有菜单,无法删除"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + AppExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/DataScopeExceptionEnum.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/DataScopeExceptionEnum.java new file mode 100644 index 000000000..ea006905d --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/DataScopeExceptionEnum.java @@ -0,0 +1,47 @@ +package cn.stylefeng.roses.kernel.system.exception.enums; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import cn.stylefeng.roses.kernel.system.constants.SystemConstants; +import lombok.Getter; + +/** + * 数据范围异常枚举 + * + * @author fengshuonan + * @date 2020/11/5 16:04 + */ +@Getter +public enum DataScopeExceptionEnum implements AbstractExceptionEnum { + + /** + * 操作失败,当前用户没有该数据的数据权限 + */ + DATA_SCOPE_ERROR(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "61", "操作失败,当前用户没有该数据的数据权限,当前数据范围是:{}"), + + /** + * 用户id为空 + */ + USER_ID_EMPTY_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "62", "获取数据范围失败,用户id为空"), + + /** + * 用户角色为空 + */ + ROLE_EMPTY_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "63", "获取数据范围失败,用户角色为空,userId:{}"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + DataScopeExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/EmployeeExceptionEnum.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/EmployeeExceptionEnum.java new file mode 100644 index 000000000..5e99c52d2 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/EmployeeExceptionEnum.java @@ -0,0 +1,47 @@ +package cn.stylefeng.roses.kernel.system.exception.enums; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import cn.stylefeng.roses.kernel.system.constants.SystemConstants; +import lombok.Getter; + +/** + * 企业员工异常 + * + * @author fengshuonan + * @date 2020/11/19 23:07 + */ +@Getter +public enum EmployeeExceptionEnum implements AbstractExceptionEnum { + + /** + * 企业员工信息不存在 + */ + EMPLOYEE_NOT_FOUND(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "21", "企业员工信息不存在,用户id:{}"), + + /** + * 用户存在多个主部门信息(系统不允许) + */ + EMPLOYEE_MANY_MAIN_NOT_FOUND(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "22", "获取用户主部门失败,用户存在多个主部门信息,用户id:{}"), + + /** + * 用户未设置主部门,或主部门信息为多个 + */ + EMPLOYEE_NOT_OR_MANY(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "23", "用户未设置主部门,或主部门信息为多个"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + EmployeeExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/OrganizationExceptionEnum.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/OrganizationExceptionEnum.java new file mode 100644 index 000000000..a707faa43 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/OrganizationExceptionEnum.java @@ -0,0 +1,52 @@ +package cn.stylefeng.roses.kernel.system.exception.enums; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import cn.stylefeng.roses.kernel.system.constants.SystemConstants; +import lombok.Getter; + +/** + * 系统管理模块相关的异常枚举 + * + * @author fengshuonan + * @date 2020/10/29 11:55 + */ +@Getter +public enum OrganizationExceptionEnum implements AbstractExceptionEnum { + + /** + * 添加组织架构失败,一级节点只有超级管理员可以添加 + */ + ADD_ONE_LEVEL_ORGANIZATION_ERROR(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "01", "添加组织架构失败,一级节点只有超级管理员可以添加"), + + /** + * 添加组织架构失败,数据权限不够 + */ + ADD_ORG_ERROR_DATA_SCOPE_NOT_ENOUGH(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "02", "添加组织架构失败,数据权限不够"), + + /** + * 查询不到组织机构,错误的组织机构ID + */ + CANT_FIND_ORG(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "03", "查询不到组织机构,错误的组织机构ID:{}"), + + /** + * 删除机构失败,该机构下有绑定员工 + */ + DELETE_ORGANIZATION_ERROR(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "04", "删除机构失败,该机构下有绑定员工"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + OrganizationExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/PositionExceptionEnum.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/PositionExceptionEnum.java new file mode 100644 index 000000000..3964c96d4 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/PositionExceptionEnum.java @@ -0,0 +1,42 @@ +package cn.stylefeng.roses.kernel.system.exception.enums; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import cn.stylefeng.roses.kernel.system.constants.SystemConstants; +import lombok.Getter; + +/** + * 职务异常 + * + * @author fengshuonan + * @date 2020/11/6 18:08 + */ +@Getter +public enum PositionExceptionEnum implements AbstractExceptionEnum { + + /** + * 找不到职务 + */ + CANT_FIND_POSITION(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "31", "查询不到组该职务,错误的职务ID:{}"), + + /** + * 职务删除失败 + */ + CANT_DELETE_POSITION(RuleConstants.BUSINESS_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "32", "职务删除失败,该职务下有关联人员"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + PositionExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/SysMenuExceptionEnum.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/SysMenuExceptionEnum.java new file mode 100644 index 000000000..1418362a4 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/SysMenuExceptionEnum.java @@ -0,0 +1,89 @@ +package cn.stylefeng.roses.kernel.system.exception.enums; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import cn.stylefeng.roses.kernel.system.constants.SystemConstants; + +/** + * 系统菜单相关异常枚举 + * + * @author fengshuonan + * @date 2020/3/26 10:12 + */ +public enum SysMenuExceptionEnum implements AbstractExceptionEnum { + + /** + * 本菜单无法修改应用,非一级菜单,不能改变所属应用 + */ + CANT_MOVE_APP(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "810", "本菜单无法修改应用,非一级菜单,不能改变所属应用"), + + /** + * 菜单不存在 + */ + MENU_NOT_EXIST(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "81", "菜单不存在"), + + /** + * 菜单编码重复 + */ + MENU_CODE_REPEAT(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "82", "菜单编码重复,请检查code参数"), + + /** + * 菜单名称重复 + */ + MENU_NAME_REPEAT(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "83", "菜单名称重复,请检查name参数"), + + /** + * 路由地址为空 + */ + MENU_ROUTER_EMPTY(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "84", "路由地址为空,请检查router参数"), + + /** + * 组件地址为空 + */ + MENU_COMPONENT_EMPTY(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "85", "组件地址为空,请检查component参数"), + + /** + * 打开方式为空 + */ + MENU_OPEN_TYPE_EMPTY(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "86", "打开方式为空,请检查openType参数"), + + /** + * 权限标识格式为空 + */ + MENU_PERMISSION_EMPTY(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "87", "权限标识为空,请检查permission参数"), + + /** + * 权限标识格式错误 + */ + MENU_PERMISSION_ERROR(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "88", "权限标识格式错误,请检查permission参数"), + + /** + * 权限不存在 + */ + MENU_PERMISSION_NOT_EXIST(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "89", "权限不存在,请检查permission参数"), + + /** + * 父级菜单不能为当前节点,请从新选择父级菜单 + */ + PID_CANT_EQ_ID(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "811", "父级菜单不能为当前节点,请从新选择父级菜单"); + + private final String errorCode; + + private final String userTip; + + SysMenuExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + + @Override + public String getErrorCode() { + return errorCode; + } + + @Override + public String getUserTip() { + return userTip; + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/SysRoleExceptionEnum.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/SysRoleExceptionEnum.java new file mode 100644 index 000000000..0172f4854 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/SysRoleExceptionEnum.java @@ -0,0 +1,71 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.system.exception.enums; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import cn.stylefeng.roses.kernel.system.constants.SystemConstants; +import lombok.Getter; + +/** + * 系统角色相关异常枚举 + * + * @author majianguo + * @date 2020/11/5 上午11:06 + */ +@Getter +public enum SysRoleExceptionEnum implements AbstractExceptionEnum { + + /** + * 角色不存在 + */ + ROLE_NOT_EXIST(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "41", "角色不存在"), + + /** + * 角色编码重复 + */ + ROLE_CODE_REPEAT(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "42", "角色编码重复,请检查code参数"), + + /** + * 角色名称重复 + */ + ROLE_NAME_REPEAT(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "43", "角色名称重复,请检查name参数"); + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + SysRoleExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/SysUserExceptionEnum.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/SysUserExceptionEnum.java new file mode 100644 index 000000000..8f745bdbf --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/exception/enums/SysUserExceptionEnum.java @@ -0,0 +1,101 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.system.exception.enums; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractExceptionEnum; +import cn.stylefeng.roses.kernel.rule.constants.RuleConstants; +import cn.stylefeng.roses.kernel.system.constants.SystemConstants; +import lombok.Getter; + +/** + * 系统用户相关异常枚举 + * + * @author luojie + * @date 2020/11/6 10:09 + */ +@Getter +public enum SysUserExceptionEnum implements AbstractExceptionEnum { + + /** + * 用户不存在 + */ + USER_NOT_EXIST(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "51", "用户不存在"), + + /** + * 账号已存在 + */ + USER_ACCOUNT_REPEAT(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "52", "账号已存在,请检查account参数"), + + /** + * 原密码错误 + */ + USER_PWD_ERROR(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "53", "原密码错误,请检查password参数"), + + /** + * 新密码与原密码相同 + */ + USER_PWD_REPEAT(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "54", "新密码与原密码相同,请检查newPassword参数"), + + /** + * 不能删除超级管理员 + */ + USER_CAN_NOT_DELETE_ADMIN(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "55", "不能删除超级管理员"), + + /** + * 不能修改超级管理员状态 + */ + USER_CAN_NOT_UPDATE_ADMIN(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "56", "不能修改超级管理员状态"), + + /** + * 请求状态值为空 + */ + REQUEST_USER_STATUS__EMPTY(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "57", "请求状态值为空"), + + /** + * 请求状值为非正确状态值 + */ + REQUEST_USER_STATUS_ERROR(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "58", "请求状态值不合法,用户状态参数不合法,参数值:{}"), + + /** + * 更新用户状态错误 + */ + UPDATE_USER_STATUS_ERROR(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + SystemConstants.SYSTEM_EXCEPTION_STEP_CODE + "59", "更新用户状态错误,更新生效数量0");; + + /** + * 错误编码 + */ + private final String errorCode; + + /** + * 提示用户信息 + */ + private final String userTip; + + SysUserExceptionEnum(String errorCode, String userTip) { + this.errorCode = errorCode; + this.userTip = userTip; + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/app/request/SysAppRequest.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/app/request/SysAppRequest.java new file mode 100644 index 000000000..cc95a7845 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/app/request/SysAppRequest.java @@ -0,0 +1,58 @@ +package cn.stylefeng.roses.kernel.system.pojo.app.request; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.validator.validators.flag.FlagValue; +import cn.stylefeng.roses.kernel.validator.validators.unique.TableUniqueValue; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 系统应用参数 + * + * @author fengshuonan + * @date 2020/3/24 20:53 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysAppRequest extends BaseRequest { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class}) + private Long id; + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空,请检查name参数", groups = {add.class, edit.class}) + @TableUniqueValue( + message = "名称存在重复,请检查name参数", + groups = {add.class, edit.class}, + tableName = "sys_app", + columnName = "name") + private String name; + + /** + * 编码 + */ + @NotBlank(message = "编码不能为空,请检查code参数", groups = {add.class, edit.class}) + @TableUniqueValue( + message = "编码存在重复,请检查code参数", + groups = {add.class, edit.class}, + tableName = "sys_app", + columnName = "code") + private String code; + + /** + * 是否默认激活(Y-是,N-否),只能有一个系统默认激活 + * 用户登录后默认展示此系统菜单 + */ + @NotBlank(message = "是否默认激活不能为空,请检查active参数", groups = {add.class, edit.class}) + @FlagValue(message = "是否默认激活格式错误,正确格式应该Y或者N,请检查active参数", groups = {add.class, edit.class}) + private String activeFlag; + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/app/response/SysAppResponse.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/app/response/SysAppResponse.java new file mode 100644 index 000000000..d8fb9baa7 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/app/response/SysAppResponse.java @@ -0,0 +1,67 @@ +package cn.stylefeng.roses.kernel.system.pojo.app.response; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 系统应用表 + * + * @author fengshuonan + * @date 2020/11/24 21:05 + */ +@Data +public class SysAppResponse implements Serializable { + + /** + * 主键id + */ + private Long id; + + /** + * 应用名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 是否默认激活(Y-是,N-否) + */ + private String activeFlag; + + /** + * 状态(字典 1启用 2禁用) + */ + private Integer statusFlag; + + /** + * 是否删除(Y-已删除,N-未删除) + */ + private String delFlag; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 创建人 + */ + private Long createUser; + + /** + * 修改时间 + */ + private Date updateTime; + + /** + * 修改人 + */ + private Long updateUser; + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/SysMenuRequest.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/SysMenuRequest.java new file mode 100644 index 000000000..bd205ffd2 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/SysMenuRequest.java @@ -0,0 +1,142 @@ +package cn.stylefeng.roses.kernel.system.pojo.menu; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.validator.validators.flag.FlagValue; +import cn.stylefeng.roses.kernel.validator.validators.unique.TableUniqueValue; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 系统菜单参数 + * + * @author fengshuonan + * @date 2020/3/26 20:42 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysMenuRequest extends BaseRequest { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class}) + private Long id; + + /** + * 父id + */ + @NotNull(message = "pid不能为空,请检查pid参数", groups = {add.class, edit.class}) + private Long pid; + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空,请检查name参数", groups = {add.class, edit.class}) + @TableUniqueValue( + message = "名称存在重复,请检查name参数", + groups = {add.class, edit.class}, + tableName = "sys_menu", + columnName = "name") + private String name; + + /** + * 编码 + */ + @NotBlank(message = "编码不能为空,请检查code参数", groups = {add.class, edit.class}) + @TableUniqueValue( + message = "编码存在重复,请检查code参数", + groups = {add.class, edit.class}, + tableName = "sys_menu", + columnName = "code") + private String code; + + /** + * 菜单类型(字典 0目录 1菜单 2按钮) + */ + @NotNull(message = "菜单类型不能为空,请检查type参数", groups = {add.class, edit.class}) + @Min(value = 0, message = "菜单类型格式错误,请检查type参数", groups = {add.class, edit.class}) + @Max(value = 2, message = "菜单类型格式错误,请检查type参数", groups = {add.class, edit.class}) + private Integer type; + + /** + * 图标 + */ + private String icon; + + /** + * 路由地址 + */ + private String router; + + /** + * 组件地址 + */ + private String component; + + /** + * 权限标识 + */ + private String permission; + + /** + * 应用分类(应用编码) + */ + @NotBlank(message = "应用分类不能为空,请检查application参数", groups = {add.class, edit.class, getAppMenus.class}) + private String appCode; + + /** + * 打开方式(字典 0无 1组件 2内链 3外链) + */ + @NotNull(message = "打开方式不能为空,请检查openType参数", groups = {add.class, edit.class}) + @Min(value = 0, message = "打开方式格式错误,请检查openType参数", groups = {add.class, edit.class}) + @Max(value = 3, message = "打开方式格式错误,请检查openType参数", groups = {add.class, edit.class}) + private Integer openType; + + /** + * 是否可见(Y-是,N-否) + */ + @NotBlank(message = "是否可见不能为空,请检查visible参数", groups = {add.class, edit.class}) + @FlagValue(message = "是否可见格式错误,正确格式应该Y或者N,请检查visible参数", groups = {add.class, edit.class}) + private String visible; + + /** + * 内链地址 + */ + private String link; + + /** + * 重定向地址 + */ + private String redirect; + + /** + * 权重(字典 1系统权重 2业务权重) + */ + @NotNull(message = "权重不能为空,请检查weight参数", groups = {add.class, edit.class}) + @Min(value = 0, message = "权重格式错误,请检查weight参数", groups = {add.class, edit.class}) + @Max(value = 2, message = "权重格式错误,请检查weight参数", groups = {add.class, edit.class}) + private Integer weight; + + /** + * 排序 + */ + @NotNull(message = "排序不能为空,请检查sort参数", groups = {add.class, edit.class}) + private Integer sort; + + /** + * 备注 + */ + private String remark; + + /** + * 获取某个应用的左侧菜单树 + */ + public @interface getAppMenus { + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/AntdBaseTreeNode.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/AntdBaseTreeNode.java new file mode 100644 index 000000000..ea41516f1 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/AntdBaseTreeNode.java @@ -0,0 +1,62 @@ +package cn.stylefeng.roses.kernel.system.pojo.menu.tree; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractTreeNode; +import lombok.Data; + +import java.util.List; + +/** + * antd通用前端树节点 + * + * @author fengshuonan + * @date 2020/6/9 12:42 + */ +@Data +public class AntdBaseTreeNode implements AbstractTreeNode { + + /** + * 主键 + */ + private Long id; + + /** + * 父id + */ + private Long parentId; + + /** + * 名称 + */ + private String title; + + /** + * 值 + */ + private String value; + + /** + * 排序,越小优先级越高 + */ + private Integer weight; + + /** + * 子节点 + */ + private List children; + + @Override + public String getNodeId() { + return this.id.toString(); + } + + @Override + public String getNodeParentId() { + return this.parentId.toString(); + } + + @Override + public void setChildrenNodes(List childrenNodes) { + this.children = children; + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/CommonBaseTreeNode.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/CommonBaseTreeNode.java new file mode 100644 index 000000000..1928ae27e --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/CommonBaseTreeNode.java @@ -0,0 +1,52 @@ +package cn.stylefeng.roses.kernel.system.pojo.menu.tree; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractTreeNode; +import lombok.Data; + +import java.util.List; + +/** + * 通用树节点 + * + * @author fengshuonan + * @date 2020/3/26 14:29 + */ +@Data +public class CommonBaseTreeNode implements AbstractTreeNode { + + /** + * 节点id + */ + private Long id; + + /** + * 节点父id + */ + private Long pid; + + /** + * 节点名称 + */ + private String nodeName; + + /** + * 子节点集合 + */ + private List children; + + @Override + public String getNodeId() { + return this.id.toString(); + } + + @Override + public String getNodeParentId() { + return this.pid.toString(); + } + + @Override + public void setChildrenNodes(List childrenNodes) { + this.children = children; + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/LoginMenuTreeNode.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/LoginMenuTreeNode.java new file mode 100644 index 000000000..24f93261b --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/LoginMenuTreeNode.java @@ -0,0 +1,87 @@ +package cn.stylefeng.roses.kernel.system.pojo.menu.tree; + +import lombok.Data; + +/** + * 登录菜单 + * + * @author fengshuonan + * @date 2020/4/17 17:35 + */ +@Data +public class LoginMenuTreeNode { + + /** + * id + */ + private Long id; + + /** + * 父id + */ + private Long pid; + + /** + * 路由名称, 必须设置,且不能重名 + */ + private String name; + + /** + * 组件 + */ + private String component; + + /** + * 重定向地址, 访问这个路由时, 自定进行重定向 + */ + private String redirect; + + /** + * 路由元信息(路由附带扩展信息) + */ + private Meta meta; + + /** + * 路径 + */ + private String path; + + /** + * 控制路由和子路由是否显示在 sidebar + */ + private boolean hidden; + + /** + * 路由元信息内部类 + */ + @Data + public class Meta { + + /** + * 路由标题, 用于显示面包屑, 页面标题 *推荐设置 + */ + public String title; + + /** + * 图标 + */ + public String icon; + + /** + * 是否可见 + */ + public boolean show; + + /** + * 如需外部打开,增加:_blank + */ + public String target; + + /** + * 内链打开http链接 + */ + public String link; + + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/MenuBaseTreeNode.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/MenuBaseTreeNode.java new file mode 100644 index 000000000..8999e7c76 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/menu/tree/MenuBaseTreeNode.java @@ -0,0 +1,62 @@ +package cn.stylefeng.roses.kernel.system.pojo.menu.tree; + +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractTreeNode; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 菜单树节点 + * + * @author fengshuonan + * @date 2020/4/5 12:03 + */ +@Data +public class MenuBaseTreeNode implements AbstractTreeNode { + + /** + * 主键 + */ + private Long id; + + /** + * 父id + */ + private Long parentId; + + /** + * 名称 + */ + private String title; + + /** + * 值 + */ + private String value; + + /** + * 排序,越小优先级越高 + */ + private BigDecimal weight; + + /** + * 子节点 + */ + private List children; + + @Override + public String getNodeId() { + return id.toString(); + } + + @Override + public String getNodeParentId() { + return this.parentId.toString(); + } + + @Override + public void setChildrenNodes(List childrenNodes) { + this.children = childrenNodes; + } +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/DataScopeResponse.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/DataScopeResponse.java new file mode 100644 index 000000000..84fdac1d8 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/DataScopeResponse.java @@ -0,0 +1,39 @@ +package cn.stylefeng.roses.kernel.system.pojo.organization; + +import cn.stylefeng.roses.kernel.auth.api.enums.DataScopeTypeEnum; +import cn.stylefeng.roses.kernel.system.pojo.role.response.SysRoleResponse; +import lombok.Data; + +import java.util.List; +import java.util.Set; + +/** + * 查询用户的数据范围时候的返回结果封装 + * + * @author fengshuonan + * @date 2020/11/6 11:30 + */ +@Data +public class DataScopeResponse { + + /** + * 数据范围类型的响应结果 + */ + private Set dataScopeTypeEnums; + + /** + * 用户id数据范围集合 + */ + private Set userIds; + + /** + * 组织架构id数据范围集合 + */ + private Set organizationIds; + + /** + * 角色信息 + */ + private List sysRoleResponses; + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysEmployeeRequest.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysEmployeeRequest.java new file mode 100644 index 000000000..ffd243226 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysEmployeeRequest.java @@ -0,0 +1,60 @@ +package cn.stylefeng.roses.kernel.system.pojo.organization; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 企业员工表,用户-组织机构的关联 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class SysEmployeeRequest extends BaseRequest { + + /** + * 主键id + */ + @NotNull(message = "主键id") + private Long id; + + /** + * 用户id + */ + @NotNull(message = "用户id不能为空", groups = {add.class}) + private Long userId; + + /** + * 所属机构id + */ + @NotNull(message = "所属机构id不能为空", groups = {add.class}) + private Long organizationId; + + /** + * 职位id集合,用逗号隔开 + */ + @NotBlank(message = "职位id集合,用逗号隔开不能为空", groups = add.class) + private String positionIds; + + /** + * 是否是主要部门,Y-是,N-否,一个人只能有一个主要部门 + */ + @NotBlank(message = "是否是主要部门,Y-是,N-否,一个人只能有一个主要部门不能为空", groups = {add.class}) + private String mainDeptFlag; + + /** + * 员工编号 + */ + private String employeeNo; + + /** + * 职位id,用在查询某个职位下的企业员工时候用 + */ + private Long positionId; + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysEmployeeResponse.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysEmployeeResponse.java new file mode 100644 index 000000000..aedfd0064 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysEmployeeResponse.java @@ -0,0 +1,58 @@ +package cn.stylefeng.roses.kernel.system.pojo.organization; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 用户员工信息 + * + * @author fengshuonan + * @date 2020/4/2 20:22 + */ +@Data +public class SysEmployeeResponse implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + private Long id; + + /** + * 用户id + */ + private Long userId; + + /** + * 所属机构id + */ + private Long organizationId; + + /** + * 所属机构名称 + */ + private String organizationName; + + /** + * 职位id集合,用逗号隔开 + */ + private String positionIds; + + /** + * 职位id名称集合,用逗号隔开 + */ + private String positionNames; + + /** + * 是否是主要部门,Y-是,N-否,一个人只能有一个主要部门 + */ + private String mainDeptFlag; + + /** + * 员工编号 + */ + private String employeeNo; + +} \ No newline at end of file diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysOrganizationRequest.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysOrganizationRequest.java new file mode 100644 index 000000000..7a946b05f --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysOrganizationRequest.java @@ -0,0 +1,76 @@ +package cn.stylefeng.roses.kernel.system.pojo.organization; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.validator.validators.status.StatusValue; +import cn.stylefeng.roses.kernel.validator.validators.unique.TableUniqueValue; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +/** + * 系统组织机构表 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class SysOrganizationRequest extends BaseRequest { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = {edit.class, delete.class, detail.class, updateStatus.class}) + private Long id; + + /** + * 父id + */ + @NotNull(message = "父id不能为空", groups = {add.class, edit.class}) + private Long pid; + + /** + * 父ids + */ + @NotBlank(message = "父ids不能为空", groups = {add.class, edit.class}) + private String pids; + + /** + * 组织名称 + */ + @NotBlank(message = "组织名称不能为空", groups = {add.class, edit.class}) + private String name; + + /** + * 组织编码 + */ + @NotBlank(message = "组织编码不能为空", groups = {add.class, edit.class}) + @TableUniqueValue( + message = "组织编码存在重复,请检查code参数", + groups = {add.class, edit.class}, + tableName = "sys_organization", + columnName = "code") + private String code; + + /** + * 排序 + */ + @NotNull(message = "排序不能为空", groups = {add.class, edit.class}) + private BigDecimal sort; + + /** + * 状态(1-启用,2-禁用) + */ + @NotNull(message = "状态(1-启用,2-禁用)不能为空", groups = {updateStatus.class}) + @StatusValue(groups = updateStatus.class) + private Integer statusFlag; + + /** + * 描述 + */ + private String remark; + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysPositionRequest.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysPositionRequest.java new file mode 100644 index 000000000..bde1a7d45 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/organization/SysPositionRequest.java @@ -0,0 +1,64 @@ +package cn.stylefeng.roses.kernel.system.pojo.organization; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.validator.validators.status.StatusValue; +import cn.stylefeng.roses.kernel.validator.validators.unique.TableUniqueValue; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +/** + * 系统职位表 + * + * @author fengshuonan + * @date 2020/11/04 11:07 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class SysPositionRequest extends BaseRequest { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = {edit.class, detail.class, delete.class}) + private Long id; + + /** + * 职位名称 + */ + @NotBlank(message = "职位名称不能为空", groups = {add.class, edit.class}) + private String name; + + /** + * 职位编码 + */ + @NotBlank(message = "职位编码不能为空", groups = {add.class, edit.class}) + @TableUniqueValue( + message = "职位编码存在重复,请检查code参数", + groups = {add.class, edit.class}, + tableName = "sys_position", + columnName = "code") + private String code; + + /** + * 排序 + */ + @NotNull(message = "排序不能为空", groups = {add.class, edit.class}) + private BigDecimal sort; + + /** + * 状态(1-启用,2-禁用) + */ + @NotNull(message = "状态(1-启用,2-禁用)不能为空", groups = {updateStatus.class}) + @StatusValue(groups = updateStatus.class) + private Integer statusFlag; + + /** + * 备注 + */ + private String remark; + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/resource/request/ResourceRequest.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/resource/request/ResourceRequest.java new file mode 100644 index 000000000..2336fb810 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/resource/request/ResourceRequest.java @@ -0,0 +1,38 @@ +package cn.stylefeng.roses.kernel.system.pojo.resource.request; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 资源请求封装 + * + * @author fengshuonan + * @since 2019-09-10 + */ +@Data +public class ResourceRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 应用编码 + */ + private String appCode; + + /** + * 资源名称 + */ + private String resourceName; + + /** + * 资源地址 + */ + private String url; + + /** + * 是否是菜单(Y-是,N-否) + */ + private String menuFlag; + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/role/request/SysRoleRequest.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/role/request/SysRoleRequest.java new file mode 100644 index 000000000..e4f671d6d --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/role/request/SysRoleRequest.java @@ -0,0 +1,118 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.system.pojo.role.request; + +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.validator.validators.unique.TableUniqueValue; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Null; +import java.math.BigDecimal; +import java.util.List; + +/** + * 系统角色参数 + * + * @author majianguo + * @date 2020/11/5 下午4:32 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysRoleRequest extends BaseRequest { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class, updateStatus.class, grantResource.class, grantData.class}) + private Long id; + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空,请检查name参数", groups = {add.class, edit.class}) + private String name; + + /** + * 编码 + */ + @NotBlank(message = "编码不能为空,请检查code参数", groups = {add.class, edit.class}) + @TableUniqueValue( + message = "编码存在重复,请检查code参数", + groups = {add.class, edit.class}, + tableName = "sys_menu", + columnName = "code") + private String code; + + /** + * 排序 + */ + @NotNull(message = "排序不能为空,请检查sort参数", groups = {add.class, edit.class}) + private BigDecimal sort; + + /** + * 数据范围类型(字典 10全部数据 20本部门及以下数据 30本部门数据 40仅本人数据 50自定义数据) + */ + @Null(message = "数据范围类型应该为空, 请移除dataScopeType参数", groups = {add.class, edit.class}) + @NotNull(message = "数据范围类型不能为空,请检查dataScopeType参数", groups = {grantData.class}) + private Integer dataScopeType; + + /** + * 备注 + */ + private String remark; + + /** + * 状态(字典 1正常 2停用) + */ + private Integer statusFlag; + + /** + * 授权资源 + */ + @NotNull(message = "授权资源不能为空,请检查grantResourceList参数", groups = {grantResource.class}) + private List grantResourceList; + + /** + * 授权数据 + */ + @NotNull(message = "授权数据不能为空,请检查grantOrgIdList参数", groups = {grantData.class}) + private List grantOrgIdList; + + /** + * 参数校验分组:授权资源 + */ + public @interface grantResource { + } + + /** + * 参数校验分组:授权数据 + */ + public @interface grantData { + } + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/role/response/SysRoleResponse.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/role/response/SysRoleResponse.java new file mode 100644 index 000000000..39d45f990 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/role/response/SysRoleResponse.java @@ -0,0 +1,55 @@ +package cn.stylefeng.roses.kernel.system.pojo.role.response; + +import cn.stylefeng.roses.kernel.auth.api.enums.DataScopeTypeEnum; +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author majianguo + * @date 2020/11/5 下午3:33 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class SysRoleResponse extends BaseRequest { + + /** + * 主键 + */ + private Long id; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 排序 + */ + private Integer sort; + + /** + * 数据范围类型(枚举 10全部数据 20本部门及以下数据 30本部门数据 40仅本人数据 50自定义数据) + */ + private Integer dataScopeType; + + /** + * 数据范围类型枚举 + */ + private DataScopeTypeEnum dataScopeTypeEnum; + + /** + * 备注 + */ + private String remark; + + /** + * 状态(字典 1正常 2停用) + */ + private Integer statusFlag; +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/user/UserLoginInfoDTO.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/user/UserLoginInfoDTO.java new file mode 100644 index 000000000..8671231b0 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/pojo/user/UserLoginInfoDTO.java @@ -0,0 +1,30 @@ +package cn.stylefeng.roses.kernel.system.pojo.user; + +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; +import lombok.Data; + +/** + * 用户登录需要用到的用户详细信息封装 + * + * @author fengshuonan + * @date 2020/10/21 9:15 + */ +@Data +public class UserLoginInfoDTO { + + /** + * 加密后的密码 + */ + private String userPasswordHexed; + + /** + * 用户状态,状态在UserStatusEnum维护 + */ + private Integer userStatus; + + /** + * 用户登录信息,用于保存当前登陆用户 + */ + private LoginUser loginUser; + +} diff --git a/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/util/DataScopeUtil.java b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/util/DataScopeUtil.java new file mode 100644 index 000000000..52beeef27 --- /dev/null +++ b/kernel-s-system/system-api/src/main/java/cn/stylefeng/roses/kernel/system/util/DataScopeUtil.java @@ -0,0 +1,121 @@ +package cn.stylefeng.roses.kernel.system.util; + +import cn.hutool.core.collection.CollectionUtil; +import cn.stylefeng.roses.kernel.auth.api.context.LoginContext; +import cn.stylefeng.roses.kernel.auth.api.enums.DataScopeTypeEnum; +import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; + +import java.util.Set; + +/** + * 数据范围相关的工具类,快捷方法 + * + * @author fengshuonan + * @date 2020/11/5 15:31 + */ +public class DataScopeUtil { + + /** + * 一句话获取当前登录用户的数据范围信息 + * + * @author fengshuonan + * @date 2020/11/5 16:07 + */ + public static String getDataScopeTip() { + + StringBuilder tips = new StringBuilder(); + + // 获取当前登录用户 + LoginUser loginUser; + try { + loginUser = LoginContext.me().getLoginUser(); + } catch (AuthException e) { + return tips.append("空:获取不到当前用户").toString(); + } + + Set dataScopeTypes = loginUser.getDataScopeTypes(); + if (dataScopeTypes == null) { + return tips.append("空:数据范围为空").toString(); + } + + tips.append("用户数据范围类型:"); + if (!dataScopeTypes.isEmpty()) { + for (DataScopeTypeEnum dataScopeType : dataScopeTypes) { + tips.append(dataScopeType.getMessage()).append(","); + } + } + + Set userDataScope = loginUser.getUserIdDataScope(); + tips.append("用户userId数据范围:"); + if (userDataScope != null && !userDataScope.isEmpty()) { + for (Long id : userDataScope) { + tips.append(id).append(","); + } + } + + Set organizationDataScope = loginUser.getOrganizationIdDataScope(); + tips.append("用户organizationId数据范围:"); + if (organizationDataScope != null && !organizationDataScope.isEmpty()) { + for (Long id : organizationDataScope) { + tips.append(id).append(","); + } + } + + return tips.toString(); + } + + /** + * 判断当前登录用户是否有某个组织架构id的数据范围 + * + * @param organizationId 被校验的组织机构id + * @author fengshuonan + * @date 2020/11/5 15:31 + */ + public static boolean validateDataScopeByOrganizationId(Long organizationId) { + + // 获取当前登录用户 + LoginUser loginUser; + try { + loginUser = LoginContext.me().getLoginUser(); + } catch (AuthException e) { + return false; + } + + // 超级管理员包含所有数据范围 + if (loginUser.getSuperAdmin()) { + return true; + } + + // 获取用户的数据范围类型,user数据范围,组织机构数据范围 + Set dataScopeTypes = loginUser.getDataScopeTypes(); + Set organizationDataScope = loginUser.getOrganizationIdDataScope(); + + // 如果数据范围类型为空,则返回没权限 + if (dataScopeTypes == null || dataScopeTypes.isEmpty()) { + return false; + } + + // 如果数据范围类型里有全部数据,则返回成功 + if (dataScopeTypes.contains(DataScopeTypeEnum.ALL)) { + return true; + } + + // 如果仅有本人的权限 + if (dataScopeTypes.size() == 1) { + DataScopeTypeEnum dataScopeTypeEnum = CollectionUtil.newArrayList(dataScopeTypes).get(0); + if (dataScopeTypeEnum.equals(DataScopeTypeEnum.SELF)) { + return false; + } + } + + // 如果部门范围为空,返回失败 + if (organizationDataScope == null || organizationDataScope.isEmpty()) { + return false; + } + + // 剩下的情况,就判断数据范围里有没有包含 organizationId + return organizationDataScope.contains(organizationId); + } + +} diff --git a/kernel-s-system/system-business-app/README.md b/kernel-s-system/system-business-app/README.md new file mode 100644 index 000000000..59ef61362 --- /dev/null +++ b/kernel-s-system/system-business-app/README.md @@ -0,0 +1 @@ +应用管理相关业务 \ No newline at end of file diff --git a/kernel-s-system/system-business-app/pom.xml b/kernel-s-system/system-business-app/pom.xml new file mode 100644 index 000000000..e14503ade --- /dev/null +++ b/kernel-s-system/system-business-app/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-system + 1.0.0 + ../pom.xml + + + system-business-app + + jar + + + + + + cn.stylefeng.roses + system-api + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/controller/SysAppController.java b/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/controller/SysAppController.java new file mode 100644 index 000000000..58be9a2a2 --- /dev/null +++ b/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/controller/SysAppController.java @@ -0,0 +1,110 @@ +package cn.stylefeng.roses.kernel.app.modular.controller; + +import cn.stylefeng.roses.kernel.app.modular.service.SysAppService; +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import cn.stylefeng.roses.kernel.system.pojo.app.request.SysAppRequest; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * 系统应用控制器 + * + * @author fengshuonan + * @date 2020/3/20 21:25 + */ +@RestController +@ApiResource(name = "系统应用") +public class SysAppController { + + @Resource + private SysAppService sysAppService; + + /** + * 添加系统应用 + * + * @author fengshuonan + * @date 2020/3/25 14:44 + */ + @PostResource(name = "添加系统应用", path = "/sysApp/add") + public ResponseData add(@RequestBody @Validated(SysAppRequest.add.class) SysAppRequest sysAppParam) { + sysAppService.add(sysAppParam); + return new SuccessResponseData(); + } + + /** + * 编辑系统应用 + * + * @author fengshuonan + * @date 2020/3/25 14:54 + */ + @PostResource(name = "编辑系统应用", path = "/sysApp/edit") + public ResponseData edit(@RequestBody @Validated(SysAppRequest.edit.class) SysAppRequest sysAppParam) { + sysAppService.edit(sysAppParam); + return new SuccessResponseData(); + } + + /** + * 删除系统应用 + * + * @author fengshuonan + * @date 2020/3/25 14:54 + */ + @PostResource(name = "删除系统应用", path = "/sysApp/delete") + public ResponseData delete(@RequestBody @Validated(SysAppRequest.delete.class) SysAppRequest sysAppParam) { + sysAppService.delete(sysAppParam); + return new SuccessResponseData(); + } + + /** + * 查看系统应用 + * + * @author fengshuonan + * @date 2020/3/26 9:49 + */ + @GetResource(name = "查看系统应用", path = "/sysApp/detail") + public ResponseData detail(@Validated(SysAppRequest.detail.class) SysAppRequest sysAppParam) { + return new SuccessResponseData(sysAppService.detail(sysAppParam)); + } + + /** + * 查询系统应用 + * + * @author fengshuonan + * @date 2020/3/20 21:23 + */ + @GetResource(name = "查询系统应用", path = "/sysApp/page") + public ResponseData page(SysAppRequest sysAppParam) { + return new SuccessResponseData(sysAppService.page(sysAppParam)); + } + + /** + * 系统应用列表 + * + * @author fengshuonan + * @date 2020/4/19 14:55 + */ + @GetResource(name = "系统应用列表", path = "/sysApp/list") + public ResponseData list(SysAppRequest sysAppParam) { + return new SuccessResponseData(sysAppService.list(sysAppParam)); + } + + /** + * 设为默认应用 + * + * @author fengshuonan + * @date 2020/6/29 16:49 + */ + @PostResource(name = "设为默认应用", path = "/sysApp/setAsDefault") + public ResponseData setAsDefault(@RequestBody @Validated(SysAppRequest.detail.class) SysAppRequest sysAppParam) { + sysAppService.setAsDefault(sysAppParam); + return new SuccessResponseData(); + } + +} diff --git a/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/entity/SysApp.java b/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/entity/SysApp.java new file mode 100644 index 000000000..e0f24ed05 --- /dev/null +++ b/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/entity/SysApp.java @@ -0,0 +1,57 @@ +package cn.stylefeng.roses.kernel.app.modular.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 系统应用表 + * + * @author fengshuonan + * @date 2020/11/24 21:05 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_app") +public class SysApp extends BaseEntity { + + /** + * 主键id + */ + @TableId("id") + private Long id; + + /** + * 应用名称 + */ + @TableField("name") + private String name; + + /** + * 编码 + */ + @TableField("code") + private String code; + + /** + * 是否默认激活(Y-是,N-否) + */ + @TableField("active_flag") + private String activeFlag; + + /** + * 状态(字典 1启用 2禁用) + */ + @TableField("status_flag") + private Integer statusFlag; + + /** + * 是否删除(Y-已删除,N-未删除) + */ + @TableField("del_flag") + private String delFlag; + +} diff --git a/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/mapper/SysAppMapper.java b/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/mapper/SysAppMapper.java new file mode 100644 index 000000000..aa3671551 --- /dev/null +++ b/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/mapper/SysAppMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.app.modular.mapper; + +import cn.stylefeng.roses.kernel.app.modular.entity.SysApp; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 系统应用mapper接口 + * + * @author fengshuonan + * @date 2020/3/13 16:17 + */ +public interface SysAppMapper extends BaseMapper { +} diff --git a/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/mapper/mapping/SysAppMapper.xml b/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/mapper/mapping/SysAppMapper.xml new file mode 100644 index 000000000..18a502137 --- /dev/null +++ b/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/mapper/mapping/SysAppMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/service/SysAppService.java b/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/service/SysAppService.java new file mode 100644 index 000000000..84cba07f1 --- /dev/null +++ b/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/service/SysAppService.java @@ -0,0 +1,108 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.app.modular.service; + +import cn.stylefeng.roses.kernel.app.modular.entity.SysApp; +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.system.pojo.app.request.SysAppRequest; + +import java.util.List; + +/** + * 系统应用service接口 + * + * @author fengshuonan + * @date 2020/3/13 16:14 + */ +public interface SysAppService extends IService { + + /** + * 添加系统应用 + * + * @param sysAppParam 添加参数 + * @author fengshuonan + * @date 2020/3/25 14:57 + */ + void add(SysAppRequest sysAppParam); + + /** + * 编辑系统应用 + * + * @param sysAppParam 编辑参数 + * @author fengshuonan + * @date 2020/3/25 14:58 + */ + void edit(SysAppRequest sysAppParam); + + /** + * 删除系统应用 + * + * @param sysAppParam 删除参数 + * @author fengshuonan + * @date 2020/3/25 14:57 + */ + void delete(SysAppRequest sysAppParam); + + /** + * 查看系统应用 + * + * @param sysAppParam 查看参数 + * @return 系统应用 + * @author fengshuonan + * @date 2020/3/26 9:50 + */ + SysApp detail(SysAppRequest sysAppParam); + + /** + * 查询系统应用 + * + * @param sysAppParam 查询参数 + * @return 查询分页结果 + * @author fengshuonan + * @date 2020/3/24 20:55 + */ + PageResult page(SysAppRequest sysAppParam); + + /** + * 系统应用列表 + * + * @param sysAppParam 查询参数 + * @return 系统应用列表 + * @author fengshuonan + * @date 2020/4/19 14:56 + */ + List list(SysAppRequest sysAppParam); + + /** + * 设为默认应用 + * + * @param sysAppParam 设为默认应用参数 + * @author fengshuonan + * @date 2020/6/29 16:49 + */ + void setAsDefault(SysAppRequest sysAppParam); + +} diff --git a/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/service/impl/SysAppServiceImpl.java b/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/service/impl/SysAppServiceImpl.java new file mode 100644 index 000000000..7464a2c56 --- /dev/null +++ b/kernel-s-system/system-business-app/src/main/java/cn/stylefeng/roses/kernel/app/modular/service/impl/SysAppServiceImpl.java @@ -0,0 +1,215 @@ +package cn.stylefeng.roses.kernel.app.modular.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.stylefeng.roses.kernel.app.modular.entity.SysApp; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.app.modular.mapper.SysAppMapper; +import cn.stylefeng.roses.kernel.app.modular.service.SysAppService; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.rule.enums.StatusEnum; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; +import cn.stylefeng.roses.kernel.rule.pojo.dict.SimpleDict; +import cn.stylefeng.roses.kernel.system.AppServiceApi; +import cn.stylefeng.roses.kernel.system.MenuServiceApi; +import cn.stylefeng.roses.kernel.system.exception.enums.AppExceptionEnum; +import cn.stylefeng.roses.kernel.system.pojo.app.request.SysAppRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 系统应用service接口实现类 + * + * @author fengshuonan + * @date 2020/3/13 16:15 + */ +@Service +public class SysAppServiceImpl extends ServiceImpl implements SysAppService, AppServiceApi { + + @Resource + private MenuServiceApi menuApi; + + @Override + public void add(SysAppRequest sysAppRequest) { + + // 检测是否有已经激活的应用,激活了就不能再设置激活了 + checkParamHaveActive(sysAppRequest, false); + + SysApp sysApp = new SysApp(); + BeanUtil.copyProperties(sysAppRequest, sysApp); + + // 设为启用 + sysApp.setStatusFlag(StatusEnum.ENABLE.getCode()); + + this.save(sysApp); + } + + @Override + public void edit(SysAppRequest sysAppRequest) { + + // 检测是否有已经激活的应用,激活了就不能再设置激活了 + checkParamHaveActive(sysAppRequest, true); + + SysApp sysApp = this.querySysApp(sysAppRequest); + BeanUtil.copyProperties(sysAppRequest, sysApp); + + //不能修改状态,用修改状态接口修改状态 + sysApp.setStatusFlag(null); + + this.updateById(sysApp); + } + + @Override + public void delete(SysAppRequest sysAppRequest) { + SysApp sysApp = this.querySysApp(sysAppRequest); + String code = sysApp.getCode(); + + // 该应用下有菜单,则不能删除 + boolean hasMenu = menuApi.hasMenu(code); + if (hasMenu) { + throw new ServiceException(AppExceptionEnum.APP_CANNOT_DELETE); + } + + // 逻辑删除 + sysApp.setDelFlag(YesOrNotEnum.Y.getCode()); + + this.updateById(sysApp); + } + + @Override + public SysApp detail(SysAppRequest sysAppRequest) { + return this.querySysApp(sysAppRequest); + } + + @Override + public PageResult page(SysAppRequest sysAppRequest) { + LambdaQueryWrapper wrapper = createWrapper(sysAppRequest); + Page page = this.page(PageFactory.defaultPage(), wrapper); + return PageResultFactory.createPageResult(page); + } + + @Override + public List list(SysAppRequest sysAppRequest) { + LambdaQueryWrapper wrapper = createWrapper(sysAppRequest); + return this.list(wrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void setAsDefault(SysAppRequest sysAppRequest) { + SysApp currentApp = this.querySysApp(sysAppRequest); + + // 所有已激活的改为未激活 + LambdaUpdateWrapper sysAppLambdaUpdateWrapper = new LambdaUpdateWrapper<>(); + sysAppLambdaUpdateWrapper.set(SysApp::getActiveFlag, YesOrNotEnum.N.getCode()); + sysAppLambdaUpdateWrapper.eq(SysApp::getActiveFlag, YesOrNotEnum.Y.getCode()); + this.update(sysAppLambdaUpdateWrapper); + + // 当前的设置为已激活 + currentApp.setActiveFlag(YesOrNotEnum.Y.getCode()); + this.updateById(currentApp); + } + + @Override + public Set getAppsByAppCodes(Set appCodes) { + HashSet simpleDicts = new HashSet<>(); + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysApp::getCode, appCodes); + queryWrapper.select(SysApp::getCode, SysApp::getId, SysApp::getName); + + List list = this.list(queryWrapper); + for (SysApp sysApp : list) { + SimpleDict simpleDict = new SimpleDict(); + simpleDict.setId(sysApp.getId()); + simpleDict.setCode(sysApp.getCode()); + simpleDict.setName(sysApp.getName()); + simpleDicts.add(simpleDict); + } + + return simpleDicts; + } + + /** + * 获取系统应用 + * + * @author fengshuonan + * @date 2020/3/26 9:56 + */ + private SysApp querySysApp(SysAppRequest sysAppRequest) { + SysApp sysApp = this.getById(sysAppRequest.getId()); + if (ObjectUtil.isNull(sysApp)) { + throw new ServiceException(AppExceptionEnum.APP_NOT_EXIST); + } + return sysApp; + } + + /** + * 检测是否有已经激活的应用,激活了就不能再设置激活了 + * + * @author fengshuonan + * @date 2020/11/24 21:29 + */ + private void checkParamHaveActive(SysAppRequest sysAppRequest, boolean excludeSelf) { + + // 查询激活状态有无已经有Y的 + LambdaQueryWrapper appQueryWrapperByActive = new LambdaQueryWrapper<>(); + appQueryWrapperByActive.eq(SysApp::getActiveFlag, YesOrNotEnum.Y.getCode()) + .eq(SysApp::getDelFlag, YesOrNotEnum.N.getCode()); + + // 排除自己 + if (excludeSelf) { + appQueryWrapperByActive.ne(SysApp::getId, sysAppRequest.getId()); + } + + int countByActive = this.count(appQueryWrapperByActive); + + // 只判断激活状态为Y时候数量是否大于1了 + if (countByActive >= 1 && YesOrNotEnum.Y.getCode().equals(sysAppRequest.getActiveFlag())) { + throw new ServiceException(AppExceptionEnum.APP_ACTIVE_REPEAT); + } + } + + /** + * 创建wrapper + * + * @author fengshuonan + * @date 2020/11/6 10:16 + */ + private LambdaQueryWrapper createWrapper(SysAppRequest sysAppRequest) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysAppRequest)) { + // 根据id查询 + if (ObjectUtil.isNotEmpty(sysAppRequest.getId())) { + queryWrapper.eq(SysApp::getId, sysAppRequest.getId()); + } + + // 根据名称模糊查询 + if (ObjectUtil.isNotEmpty(sysAppRequest.getName())) { + queryWrapper.like(SysApp::getName, sysAppRequest.getName()); + } + + // 根据编码模糊查询 + if (ObjectUtil.isNotEmpty(sysAppRequest.getCode())) { + queryWrapper.like(SysApp::getCode, sysAppRequest.getCode()); + } + } + + // 查询未删除状态的 + queryWrapper.eq(SysApp::getDelFlag, YesOrNotEnum.N.getCode()); + + return queryWrapper; + } + +} diff --git a/kernel-s-system/system-business-menu/README.md b/kernel-s-system/system-business-menu/README.md new file mode 100644 index 000000000..a1f0387f3 --- /dev/null +++ b/kernel-s-system/system-business-menu/README.md @@ -0,0 +1 @@ +菜单管理相关的业务 \ No newline at end of file diff --git a/kernel-s-system/system-business-menu/pom.xml b/kernel-s-system/system-business-menu/pom.xml new file mode 100644 index 000000000..50b553d68 --- /dev/null +++ b/kernel-s-system/system-business-menu/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-system + 1.0.0 + ../pom.xml + + + system-business-menu + + jar + + + + + + cn.stylefeng.roses + system-api + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/controller/SysMenuController.java b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/controller/SysMenuController.java new file mode 100644 index 000000000..144af4865 --- /dev/null +++ b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/controller/SysMenuController.java @@ -0,0 +1,120 @@ +package cn.stylefeng.roses.kernel.menu.modular.controller; + +import cn.stylefeng.roses.kernel.menu.modular.service.SysMenuService; +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import cn.stylefeng.roses.kernel.system.pojo.menu.SysMenuRequest; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * 系统菜单控制器 + * + * @author fengshuonan + * @date 2020/3/20 18:54 + */ +@RestController +@ApiResource(name = "菜单管理") +public class SysMenuController { + + @Resource + private SysMenuService sysMenuService; + + /** + * 添加系统菜单 + * + * @author fengshuonan + * @date 2020/3/27 8:57 + */ + @PostResource(name = "添加系统菜单", path = "/sysMenu/add") + public ResponseData add(@RequestBody @Validated(SysMenuRequest.add.class) SysMenuRequest sysMenuRequest) { + sysMenuService.add(sysMenuRequest); + return new SuccessResponseData(); + } + + /** + * 编辑系统菜单 + * + * @author fengshuonan + * @date 2020/3/27 8:59 + */ + @PostResource(name = "编辑系统菜单", path = "/sysMenu/edit") + public ResponseData edit(@RequestBody @Validated(SysMenuRequest.edit.class) SysMenuRequest sysMenuRequest) { + sysMenuService.edit(sysMenuRequest); + return new SuccessResponseData(); + } + + /** + * 删除系统菜单 + * + * @author fengshuonan + * @date 2020/3/27 8:58 + */ + @PostResource(name = "删除系统菜单", path = "/sysMenu/delete") + public ResponseData delete(@RequestBody @Validated(SysMenuRequest.delete.class) SysMenuRequest sysMenuRequest) { + sysMenuService.delete(sysMenuRequest); + return new SuccessResponseData(); + } + + /** + * 查看系统菜单 + * + * @author fengshuonan + * @date 2020/3/27 9:01 + */ + @GetResource(name = "查看系统菜单", path = "/sysMenu/detail") + public ResponseData detail(@Validated(SysMenuRequest.detail.class) SysMenuRequest sysMenuRequest) { + return new SuccessResponseData(sysMenuService.detail(sysMenuRequest)); + } + + /** + * 系统菜单列表,树形结构,用于菜单管理界面的列表展示 + * + * @author fengshuonan + * @date 2020/3/20 21:23 + */ + @GetResource(name = "系统菜单列表(树)", path = "/sysMenu/list") + public ResponseData list(SysMenuRequest sysMenuRequest) { + return new SuccessResponseData(sysMenuService.list(sysMenuRequest)); + } + + /** + * 获取某个应用的菜单,用于系统顶部切换菜单 + * + * @author fengshuonan + * @date 2020/4/19 15:50 + */ + @GetResource(name = "获取某个应用的菜单", path = "/sysMenu/getAppMenus") + public ResponseData getAppMenus(@RequestBody @Validated(SysMenuRequest.getAppMenus.class) SysMenuRequest sysMenuRequest) { + return new SuccessResponseData(sysMenuService.getAppMenusAntDesign(sysMenuRequest.getAppCode())); + } + + /** + * 获取系统菜单树,用于新增,编辑时选择上级节点 + * + * @author fengshuonan + * @date 2020/3/27 15:55 + */ + @GetResource(name = "获取系统菜单树,用于新增,编辑时选择上级节点", path = "/sysMenu/tree") + public ResponseData tree(SysMenuRequest sysMenuRequest) { + return new SuccessResponseData(sysMenuService.tree(sysMenuRequest)); + } + + /** + * 获取系统菜单树,用于给角色授权时选择 + * + * @author fengshuonan + * @date 2020/4/5 15:00 + */ + @GetResource(name = "获取系统菜单树,用于给角色授权时选择", path = "/sysMenu/treeForGrant") + public ResponseData treeForGrant(SysMenuRequest sysMenuRequest) { + return new SuccessResponseData(sysMenuService.treeForGrant(sysMenuRequest)); + } + +} diff --git a/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/entity/SysMenu.java b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/entity/SysMenu.java new file mode 100644 index 000000000..6f85aea26 --- /dev/null +++ b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/entity/SysMenu.java @@ -0,0 +1,147 @@ +package cn.stylefeng.roses.kernel.menu.modular.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import cn.stylefeng.roses.kernel.rule.abstracts.AbstractTreeNode; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 系统菜单 + * + * @author stylefeng + * @date 2020/11/22 21:16 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_menu") +public class SysMenu extends BaseEntity implements AbstractTreeNode { + + /** + * 主键 + */ + @TableId("id") + private Long id; + + /** + * 父id,顶级节点的父id是0 + */ + @TableField("pid") + private Long pid; + + /** + * 父id集合,中括号包住,逗号分隔 + */ + @TableField("pids") + private String pids; + + /** + * 名称 + */ + @TableField("name") + private String name; + + /** + * 菜单的编码 + */ + @TableField("code") + private String code; + + /** + * 应用分类(应用编码) + */ + @TableField("app_code") + private String appCode; + + /** + * 是否可见(Y-是,N-否) + */ + @TableField("visible") + private String visible; + + /** + * 排序 + */ + @TableField("sort") + private BigDecimal sort; + + /** + * 状态(1-启用,2-禁用) + */ + @TableField("status_flag") + private Integer statusFlag; + + /** + * 关联的资源的编码 + */ + @TableField("resource_code") + private String resourceCode; + + /** + * 图标 + */ + @TableField("icon") + private String icon; + + /** + * 路由地址,浏览器显示的URL,例如/menu + */ + @TableField("router") + private String router; + + /** + * 前端组件名 + */ + @TableField("component") + private String component; + + /** + * 外部链接打开方式(1内置外链 2新页面外链) + */ + @TableField("link_open_type") + private Integer linkOpenType; + + /** + * 外部链接地址 + */ + @TableField("link_url") + private String linkUrl; + + /** + * 备注 + */ + @TableField("remark") + private String remark; + + /** + * 是否删除,Y-被删除,N-未删除 + */ + @TableField("del_flag") + private String delFlag; + + /** + * 子节点(表中不存在,用于构造树) + */ + @TableField(exist = false) + private List children; + + @Override + public String getNodeId() { + return id.toString(); + } + + @Override + public String getNodeParentId() { + return pid.toString(); + } + + @Override + public void setChildrenNodes(List childrenNodes) { + this.children = childrenNodes; + } +} diff --git a/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/factory/MenuFactory.java b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/factory/MenuFactory.java new file mode 100644 index 000000000..94cc6c193 --- /dev/null +++ b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/factory/MenuFactory.java @@ -0,0 +1,74 @@ +package cn.stylefeng.roses.kernel.menu.modular.factory; + +import cn.hutool.core.collection.CollectionUtil; +import cn.stylefeng.roses.kernel.menu.modular.entity.SysMenu; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import cn.stylefeng.roses.kernel.system.enums.LinkOpenTypeEnum; +import cn.stylefeng.roses.kernel.system.pojo.menu.tree.LoginMenuTreeNode; +import cn.stylefeng.roses.kernel.system.pojo.menu.tree.MenuBaseTreeNode; + +import java.util.List; + +/** + * 菜单pojo的组装 + * + * @author fengshuonan + * @date 2020/11/23 21:58 + */ +public class MenuFactory { + + /** + * menu实体转化为菜单树节点 + * + * @author fengshuonan + * @date 2020/11/23 21:54 + */ + public static MenuBaseTreeNode parseMenuBaseTreeNode(SysMenu sysMenu) { + MenuBaseTreeNode menuTreeNode = new MenuBaseTreeNode(); + menuTreeNode.setId(sysMenu.getId()); + menuTreeNode.setParentId(sysMenu.getPid()); + menuTreeNode.setValue(String.valueOf(sysMenu.getId())); + menuTreeNode.setTitle(sysMenu.getName()); + menuTreeNode.setWeight(sysMenu.getSort()); + return menuTreeNode; + } + + /** + * 将SysMenu格式菜单转换为LoginMenuTreeNode菜单 + * + * @author fengshuonan + * @date 2020/4/17 17:53 + */ + public static List convertSysMenuToLoginMenu(List sysMenuList) { + List antDesignMenuTreeNodeList = CollectionUtil.newArrayList(); + sysMenuList.forEach(sysMenu -> { + LoginMenuTreeNode loginMenuTreeNode = new LoginMenuTreeNode(); + loginMenuTreeNode.setComponent(sysMenu.getComponent()); + loginMenuTreeNode.setId(sysMenu.getId()); + loginMenuTreeNode.setName(sysMenu.getCode()); + loginMenuTreeNode.setPath(sysMenu.getRouter()); + loginMenuTreeNode.setPid(sysMenu.getPid()); + LoginMenuTreeNode.Meta mateItem = new LoginMenuTreeNode().new Meta(); + mateItem.setIcon(sysMenu.getIcon()); + mateItem.setTitle(sysMenu.getName()); + mateItem.setLink(sysMenu.getLinkUrl()); + + // 是否可见 + mateItem.setShow(!YesOrNotEnum.N.getCode().equals(sysMenu.getVisible())); + + // 是否是外链 + if (LinkOpenTypeEnum.INNER.getCode().equals(sysMenu.getLinkOpenType())) { + + // 打开外链 + mateItem.setTarget("_blank"); + loginMenuTreeNode.setPath(sysMenu.getLinkUrl()); + loginMenuTreeNode.setRedirect(sysMenu.getLinkUrl()); + + } + loginMenuTreeNode.setMeta(mateItem); + antDesignMenuTreeNodeList.add(loginMenuTreeNode); + }); + return antDesignMenuTreeNodeList; + } + +} diff --git a/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/mapper/SysMenuMapper.java b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/mapper/SysMenuMapper.java new file mode 100644 index 000000000..0872ded56 --- /dev/null +++ b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/mapper/SysMenuMapper.java @@ -0,0 +1,13 @@ +package cn.stylefeng.roses.kernel.menu.modular.mapper; + +import cn.stylefeng.roses.kernel.menu.modular.entity.SysMenu; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 系统菜单mapper接口 + * + * @author fengshuonan + * @date 2020/3/13 16:05 + */ +public interface SysMenuMapper extends BaseMapper { +} diff --git a/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/mapper/mapping/SysMenuMapper.xml b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/mapper/mapping/SysMenuMapper.xml new file mode 100644 index 000000000..faa60a0ba --- /dev/null +++ b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/mapper/mapping/SysMenuMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/service/SysMenuService.java b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/service/SysMenuService.java new file mode 100644 index 000000000..6ab98a932 --- /dev/null +++ b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/service/SysMenuService.java @@ -0,0 +1,120 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.menu.modular.service; + +import cn.stylefeng.roses.kernel.menu.modular.entity.SysMenu; +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.system.pojo.menu.SysMenuRequest; +import cn.stylefeng.roses.kernel.system.pojo.menu.tree.LoginMenuTreeNode; +import cn.stylefeng.roses.kernel.system.pojo.menu.tree.MenuBaseTreeNode; + +import java.util.List; + +/** + * 系统菜单service接口 + * + * @author fengshuonan + * @date 2020/3/13 16:05 + */ +public interface SysMenuService extends IService { + + /** + * 添加系统菜单 + * + * @param sysMenuRequest 添加参数 + * @author fengshuonan + * @date 2020/3/27 9:03 + */ + void add(SysMenuRequest sysMenuRequest); + + /** + * 编辑系统菜单 + * + * @param sysMenuRequest 编辑参数 + * @author fengshuonan + * @date 2020/3/27 9:03 + */ + void edit(SysMenuRequest sysMenuRequest); + + /** + * 删除系统菜单 + * + * @param sysMenuRequest 删除参数 + * @author fengshuonan + * @date 2020/3/27 9:03 + */ + void delete(SysMenuRequest sysMenuRequest); + + /** + * 查看系统菜单 + * + * @param sysMenuRequest 查看参数 + * @return 系统菜单 + * @author fengshuonan + * @date 2020/3/27 9:03 + */ + SysMenu detail(SysMenuRequest sysMenuRequest); + + /** + * 系统菜单列表,树形结构,用于菜单管理界面的列表展示 + * + * @param sysMenuRequest 查询参数 + * @return 菜单树表列表 + * @author fengshuonan + * @date 2020/3/26 10:19 + */ + List list(SysMenuRequest sysMenuRequest); + + /** + * 获取某个应用的菜单,用于系统顶部切换菜单(AntDesign前端框架) + * + * @param appCode 应用编码 + * @return AntDesign菜单信息结果集 + * @author fengshuonan + * @date 2020/4/17 17:48 + */ + List getAppMenusAntDesign(String appCode); + + /** + * 获取系统菜单树,用于新增,编辑时选择上级节点 + * + * @param sysMenuRequest 查询参数 + * @return 菜单树列表 + * @author fengshuonan + * @date 2020/3/27 15:56 + */ + List tree(SysMenuRequest sysMenuRequest); + + /** + * 获取系统菜单树,用于给角色授权时选择 + * + * @param sysMenuRequest 查询参数 + * @return 菜单树列表 + * @author fengshuonan + * @date 2020/4/5 15:01 + */ + List treeForGrant(SysMenuRequest sysMenuRequest); + +} diff --git a/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/service/impl/SysMenuServiceImpl.java b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/service/impl/SysMenuServiceImpl.java new file mode 100644 index 000000000..e796fbd84 --- /dev/null +++ b/kernel-s-system/system-business-menu/src/main/java/cn/stylefeng/roses/kernel/menu/modular/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,398 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.menu.modular.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.stylefeng.roses.kernel.menu.modular.entity.SysMenu; +import cn.stylefeng.roses.kernel.menu.modular.factory.MenuFactory; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.auth.api.context.LoginContext; +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; +import cn.stylefeng.roses.kernel.db.api.DbOperatorApi; +import cn.stylefeng.roses.kernel.menu.modular.mapper.SysMenuMapper; +import cn.stylefeng.roses.kernel.menu.modular.service.SysMenuService; +import cn.stylefeng.roses.kernel.rule.enums.StatusEnum; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; +import cn.stylefeng.roses.kernel.rule.factory.DefaultTreeBuildFactory; +import cn.stylefeng.roses.kernel.rule.pojo.dict.SimpleDict; +import cn.stylefeng.roses.kernel.system.MenuServiceApi; +import cn.stylefeng.roses.kernel.system.RoleServiceApi; +import cn.stylefeng.roses.kernel.system.constants.SymbolConstant; +import cn.stylefeng.roses.kernel.system.constants.SystemConstants; +import cn.stylefeng.roses.kernel.system.exception.enums.SysMenuExceptionEnum; +import cn.stylefeng.roses.kernel.system.pojo.menu.SysMenuRequest; +import cn.stylefeng.roses.kernel.system.pojo.menu.tree.LoginMenuTreeNode; +import cn.stylefeng.roses.kernel.system.pojo.menu.tree.MenuBaseTreeNode; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 系统菜单service接口实现类 + * + * @author fengshuonan + * @date 2020/3/13 16:05 + */ +@Service +public class SysMenuServiceImpl extends ServiceImpl implements SysMenuService, MenuServiceApi { + + @Resource + private DbOperatorApi dbOperatorApi; + + @Resource + private RoleServiceApi roleServiceApi; + + @Override + public void add(SysMenuRequest sysMenuRequest) { + + SysMenu sysMenu = new SysMenu(); + BeanUtil.copyProperties(sysMenuRequest, sysMenu); + + // 组装pids + String newPids = createPids(sysMenuRequest.getPid()); + sysMenu.setPids(newPids); + + // 设置启用状态 + sysMenu.setStatusFlag(StatusEnum.ENABLE.getCode()); + + this.save(sysMenu); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void edit(SysMenuRequest sysMenuRequest) { + + // 获取库中的菜单信息 + SysMenu oldMenu = this.querySysMenu(sysMenuRequest); + + String newPids = updateChildrenAppAndLevel(sysMenuRequest, oldMenu); + + // 拷贝参数到实体中 + BeanUtil.copyProperties(sysMenuRequest, oldMenu); + + // 设置新的pids + oldMenu.setPids(newPids); + + // 不能修改状态,用修改状态接口修改状态 + oldMenu.setStatusFlag(null); + + this.updateById(oldMenu); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(SysMenuRequest sysMenuRequest) { + + Long id = sysMenuRequest.getId(); + + // 获取所有子级的节点id + Set childIdList = this.dbOperatorApi.findSubListByParentId( + "sys_menu", "pids", "id", id); + childIdList.add(id); + + // 逻辑删除,设置删除标识 + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper + .in(SysMenu::getId, childIdList) + .set(SysMenu::getDelFlag, YesOrNotEnum.Y.getCode()); + this.update(updateWrapper); + } + + @Override + public SysMenu detail(SysMenuRequest sysMenuRequest) { + return this.querySysMenu(sysMenuRequest); + } + + @Override + public List list(SysMenuRequest sysMenuRequest) { + + LambdaQueryWrapper wrapper = createWrapper(sysMenuRequest); + + List sysMenuList = this.list(wrapper); + + // 将结果集处理成树 + return new DefaultTreeBuildFactory().doTreeBuild(sysMenuList); + } + + @Override + public List getAppMenusAntDesign(String appCode) { + + List menuIdList = getCurrentUserMenuIds(); + + // 获取菜单列表 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysMenu::getId, menuIdList) + .eq(SysMenu::getStatusFlag, StatusEnum.ENABLE.getCode()) + .eq(SysMenu::getDelFlag, YesOrNotEnum.N.getCode()) + .eq(SysMenu::getAppCode, appCode) + .orderByAsc(SysMenu::getSort); + List sysMenuList = this.list(queryWrapper); + + // 转换成登录菜单格式 + return MenuFactory.convertSysMenuToLoginMenu(sysMenuList); + } + + @Override + public List tree(SysMenuRequest sysMenuRequest) { + List menuTreeNodeList = CollectionUtil.newArrayList(); + + LambdaQueryWrapper wrapper = createWrapper(sysMenuRequest); + this.list(wrapper).forEach(sysMenu -> { + MenuBaseTreeNode menuTreeNode = MenuFactory.parseMenuBaseTreeNode(sysMenu); + menuTreeNodeList.add(menuTreeNode); + }); + + return new DefaultTreeBuildFactory().doTreeBuild(menuTreeNodeList); + } + + @Override + public List treeForGrant(SysMenuRequest sysMenuRequest) { + List menuTreeNodeList = CollectionUtil.newArrayList(); + + LambdaQueryWrapper wrapper = createWrapper(sysMenuRequest); + wrapper.eq(SysMenu::getStatusFlag, StatusEnum.ENABLE.getCode()); + + // 非超级管理员则获取自己拥有的菜单,分配给人员,防止越级授权 + if (!LoginContext.me().getSuperAdminFlag()) { + List menuIdList = getCurrentUserMenuIds(); + if (!menuIdList.isEmpty()) { + wrapper.in(SysMenu::getId, menuIdList); + } + } + + this.list(wrapper).forEach(sysMenu -> { + MenuBaseTreeNode menuTreeNode = MenuFactory.parseMenuBaseTreeNode(sysMenu); + menuTreeNodeList.add(menuTreeNode); + }); + + return new DefaultTreeBuildFactory().doTreeBuild(menuTreeNodeList); + } + + @Override + public boolean hasMenu(String appCode) { + SysMenuRequest sysMenuRequest = new SysMenuRequest(); + sysMenuRequest.setAppCode(appCode); + LambdaQueryWrapper wrapper = this.createWrapper(sysMenuRequest); + + List list = this.list(wrapper); + return !list.isEmpty(); + } + + @Override + public List getMenuIdsByResourceCodes(List resourceCodes) { + + if (ObjectUtil.isEmpty(resourceCodes)) { + return new ArrayList<>(); + } + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 根据资源编码集合查询 + queryWrapper.in(SysMenu::getResourceCode, resourceCodes); + + // 查询未删除状态的 + queryWrapper.eq(SysMenu::getDelFlag, YesOrNotEnum.N.getCode()); + + // 只查询菜单id + queryWrapper.select(SysMenu::getId); + + List list = this.list(queryWrapper); + return list.stream().map(SysMenu::getId).collect(Collectors.toList()); + } + + /** + * 获取系统菜单 + * + * @author fengshuonan + * @date 2020/3/27 9:13 + */ + private SysMenu querySysMenu(SysMenuRequest sysMenuRequest) { + SysMenu sysMenu = this.getById(sysMenuRequest.getId()); + if (ObjectUtil.isNull(sysMenu)) { + throw new ServiceException(SysMenuExceptionEnum.MENU_NOT_EXIST); + } + return sysMenu; + } + + /** + * 创建pids的值 + *

+ * 如果pid是0顶级节点,pids就是 [0], + *

+ * 如果pid不是顶级节点,pids就是父菜单的pids + [pid] + , + * + * @author fengshuonan, stylefeng + * @date 2020/3/26 11:28 + */ + private String createPids(Long pid) { + if (pid.equals(SystemConstants.DEFAULT_PARENT_ID)) { + return SymbolConstant.LEFT_SQUARE_BRACKETS + SystemConstants.DEFAULT_PARENT_ID + SymbolConstant.RIGHT_SQUARE_BRACKETS + + SymbolConstant.COMMA; + } else { + //获取父菜单 + SysMenu parentMenu = this.getById(pid); + return parentMenu.getPids() + + SymbolConstant.LEFT_SQUARE_BRACKETS + pid + SymbolConstant.RIGHT_SQUARE_BRACKETS + + SymbolConstant.COMMA; + } + } + + /** + * 创建查询条件wrapper + * + * @author fengshuonan + * @date 2020/11/6 10:16 + */ + private LambdaQueryWrapper createWrapper(SysMenuRequest sysMenuRequest) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysMenuRequest)) { + + // 根据所属应用查询 + if (ObjectUtil.isNotEmpty(sysMenuRequest.getAppCode())) { + queryWrapper.eq(SysMenu::getAppCode, sysMenuRequest.getAppCode()); + } + + // 根据菜单名称模糊查询 + if (ObjectUtil.isNotEmpty(sysMenuRequest.getName())) { + queryWrapper.like(SysMenu::getName, sysMenuRequest.getName()); + } + } + + // 查询未删除状态的 + queryWrapper.eq(SysMenu::getDelFlag, YesOrNotEnum.N.getCode()); + + // 根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysMenu::getSort); + + return queryWrapper; + } + + /** + * 获取当前用户的菜单id集合 + * + * @author fengshuonan + * @date 2020/11/22 23:15 + */ + private List getCurrentUserMenuIds() { + + // 获取当前用户的角色id集合 + LoginUser loginUser = LoginContext.me().getLoginUser(); + List roleIdList = loginUser.getRoles().stream().map(SimpleDict::getId).collect(Collectors.toList()); + + // 当前用户角色为空,则没菜单 + if (ObjectUtil.isEmpty(roleIdList)) { + return CollectionUtil.newArrayList(); + } + + // 获取角色拥有的菜单id集合 + List menuIdList = roleServiceApi.getMenuIdsByRoleIds(roleIdList); + if (ObjectUtil.isEmpty(menuIdList)) { + return CollectionUtil.newArrayList(); + } + + return menuIdList; + } + + /** + * 更新子节点以及子节点的子节点的appCode和层级关系(pids) + * + * @author fengshuonan + * @date 2020/11/23 22:05 + */ + private String updateChildrenAppAndLevel(SysMenuRequest sysMenuRequest, SysMenu oldMenu) { + + // 本菜单旧的pids + Long oldPid = oldMenu.getPid(); + String oldPids = oldMenu.getPids(); + + // 生成新的pid和pids + Long newPid = sysMenuRequest.getPid(); + String newPids = this.createPids(sysMenuRequest.getPid()); + + // 是否更新子应用的标识 + boolean updateSubAppsFlag = false; + + // 是否更新子节点的pids的标识 + boolean updateSubPidsFlag = false; + + // 如果应用有变化,不能把非一级菜单转移应用 + if (!sysMenuRequest.getAppCode().equals(oldMenu.getAppCode())) { + if (!oldPid.equals(SystemConstants.DEFAULT_PARENT_ID)) { + throw new ServiceException(SysMenuExceptionEnum.CANT_MOVE_APP); + } + updateSubAppsFlag = true; + } + + // 父节点有变化 + if (!newPid.equals(oldPid)) { + updateSubPidsFlag = true; + } + + // 开始更新所有子节点的配置 + if (updateSubAppsFlag || updateSubPidsFlag) { + + // 查找所有叶子节点,包含子节点的子节点 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.like(SysMenu::getPids, oldMenu.getId()); + List list = this.list(queryWrapper); + + // 更新所有子节点的应用为当前菜单的应用 + if (ObjectUtil.isNotEmpty(list)) { + + // 更新所有子节点的application + if (updateSubAppsFlag) { + list.forEach(child -> child.setAppCode(sysMenuRequest.getAppCode())); + } + + // 更新所有子节点的pids + if (updateSubPidsFlag) { + list.forEach(child -> { + // 子节点pids组成 = 当前菜单新pids + 当前菜单id + 子节点自己的pids后缀 + String oldParentCodesPrefix = oldPids + SymbolConstant.LEFT_SQUARE_BRACKETS + oldMenu.getId() + + SymbolConstant.RIGHT_SQUARE_BRACKETS + SymbolConstant.COMMA; + String oldParentCodesSuffix = child.getPids().substring(oldParentCodesPrefix.length()); + String menuParentCodes = newPids + SymbolConstant.LEFT_SQUARE_BRACKETS + oldMenu.getId() + + SymbolConstant.RIGHT_SQUARE_BRACKETS + SymbolConstant.COMMA + oldParentCodesSuffix; + child.setPids(menuParentCodes); + }); + } + + this.updateBatchById(list); + } + } + return newPids; + } + +} diff --git a/kernel-s-system/system-business-organization/README.md b/kernel-s-system/system-business-organization/README.md new file mode 100644 index 000000000..cf59f8940 --- /dev/null +++ b/kernel-s-system/system-business-organization/README.md @@ -0,0 +1 @@ +组织架构的维护 \ No newline at end of file diff --git a/kernel-s-system/system-business-organization/pom.xml b/kernel-s-system/system-business-organization/pom.xml new file mode 100644 index 000000000..0810343a4 --- /dev/null +++ b/kernel-s-system/system-business-organization/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-system + 1.0.0 + ../pom.xml + + + system-business-organization + + jar + + + + + + cn.stylefeng.roses + system-api + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/controller/SysOrganizationController.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/controller/SysOrganizationController.java new file mode 100644 index 000000000..093001be1 --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/controller/SysOrganizationController.java @@ -0,0 +1,111 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.controller; + +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import cn.stylefeng.roses.kernel.system.modular.organization.service.SysOrganizationService; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysOrganizationRequest; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * 系统组织机构控制器 + * + * @author fengshuonan + * @date 2020/11/18 21:55 + */ +@RestController +@ApiResource(name = "系统组织机构管理") +public class SysOrganizationController { + + @Resource + private SysOrganizationService sysorganizationService; + + /** + * 添加系统组织机构 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + @PostResource(name = "添加系统组织机构", path = "/sysOrganization/add") + public ResponseData add(@RequestBody @Validated(SysOrganizationRequest.add.class) SysOrganizationRequest sysOrganizationRequest) { + sysorganizationService.add(sysOrganizationRequest); + return new SuccessResponseData(); + } + + /** + * 编辑系统组织机构 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + @PostResource(name = "编辑系统组织机构", path = "/sysOrganization/edit") + public ResponseData edit(@RequestBody @Validated(SysOrganizationRequest.edit.class) SysOrganizationRequest sysOrganizationRequest) { + sysorganizationService.edit(sysOrganizationRequest); + return new SuccessResponseData(); + } + + /** + * 删除系统组织机构 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + @PostResource(name = "删除系统组织机构", path = "/sysOrganization/delete") + public ResponseData delete(@RequestBody @Validated(SysOrganizationRequest.delete.class) SysOrganizationRequest sysOrganizationRequest) { + sysorganizationService.delete(sysOrganizationRequest); + return new SuccessResponseData(); + } + + /** + * 修改组织机构状态 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + @PostResource(name = "修改组织机构状态", path = "/sysOrganization/updateStatus") + public ResponseData updateStatus(@RequestBody @Validated(SysOrganizationRequest.updateStatus.class) SysOrganizationRequest sysOrganizationRequest) { + sysorganizationService.updateStatus(sysOrganizationRequest); + return new SuccessResponseData(); + } + + /** + * 查看详情系统组织机构 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + @GetResource(name = "查看详情系统组织机构", path = "/sysOrganization/detail") + public ResponseData detail(@Validated(SysOrganizationRequest.detail.class) SysOrganizationRequest sysOrganizationRequest) { + return new SuccessResponseData(sysorganizationService.detail(sysOrganizationRequest)); + } + + /** + * 分页查询系统组织机构 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + @GetResource(name = "分页查询系统组织机构", path = "/sysOrganization/page") + public ResponseData page(SysOrganizationRequest sysOrganizationRequest) { + return new SuccessResponseData(sysorganizationService.page(sysOrganizationRequest)); + } + + /** + * 获取全部系统组织机构 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + @GetResource(name = "获取全部系统组织机构", path = "/sysOrganization/list") + public ResponseData list(SysOrganizationRequest sysOrganizationRequest) { + return new SuccessResponseData(sysorganizationService.list(sysOrganizationRequest)); + } + + +} diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/controller/SysPositionController.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/controller/SysPositionController.java new file mode 100644 index 000000000..81fb73827 --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/controller/SysPositionController.java @@ -0,0 +1,111 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.controller; + +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import cn.stylefeng.roses.kernel.system.modular.organization.service.SysPositionService; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysPositionRequest; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * 系统职位控制器 + * + * @author fengshuonan + * @date 2020/11/18 21:56 + */ +@RestController +@ApiResource(name = "系统职位管理") +public class SysPositionController { + + @Resource + private SysPositionService syspositionService; + + /** + * 添加系统职位 + * + * @author fengshuonan + * @date 2020/11/04 11:07 + */ + @PostResource(name = "添加系统职位", path = "/sysPosition/add") + public ResponseData add(@RequestBody @Validated(SysPositionRequest.add.class) SysPositionRequest sysPositionRequest) { + syspositionService.add(sysPositionRequest); + return new SuccessResponseData(); + } + + /** + * 编辑系统职位 + * + * @author fengshuonan + * @date 2020/11/04 11:07 + */ + @PostResource(name = "编辑系统职位", path = "/sysPosition/edit") + public ResponseData edit(@RequestBody @Validated(SysPositionRequest.edit.class) SysPositionRequest sysPositionRequest) { + syspositionService.edit(sysPositionRequest); + return new SuccessResponseData(); + } + + /** + * 删除系统职位 + * + * @author fengshuonan + * @date 2020/11/04 11:07 + */ + @PostResource(name = "删除系统职位", path = "/sysPosition/delete") + public ResponseData delete(@RequestBody @Validated(SysPositionRequest.delete.class) SysPositionRequest sysPositionRequest) { + syspositionService.delete(sysPositionRequest); + return new SuccessResponseData(); + } + + /** + * 更新职位状态 + * + * @author fengshuonan + * @date 2020/11/04 11:07 + */ + @PostResource(name = "更新职位状态", path = "/sysPosition/updateStatus") + public ResponseData updateStatus(@RequestBody @Validated(BaseRequest.updateStatus.class) SysPositionRequest sysPositionRequest) { + syspositionService.updateStatus(sysPositionRequest); + return new SuccessResponseData(); + } + + /** + * 查看详情系统职位 + * + * @author fengshuonan + * @date 2020/11/04 11:07 + */ + @GetResource(name = "查看详情系统职位", path = "/sysPosition/detail") + public ResponseData detail(@Validated(SysPositionRequest.detail.class) SysPositionRequest sysPositionRequest) { + return new SuccessResponseData(syspositionService.detail(sysPositionRequest)); + } + + /** + * 分页查询系统职位 + * + * @author fengshuonan + * @date 2020/11/04 11:07 + */ + @GetResource(name = "分页查询系统职位", path = "/sysPosition/page") + public ResponseData page(SysPositionRequest sysPositionRequest) { + return new SuccessResponseData(syspositionService.page(sysPositionRequest)); + } + + /** + * 获取全部系统职位 + * + * @author fengshuonan + * @date 2020/11/04 11:07 + */ + @GetResource(name = "获取全部系统职位", path = "/sysPosition/list") + public ResponseData list(SysPositionRequest sysPositionRequest) { + return new SuccessResponseData(syspositionService.list(sysPositionRequest)); + } + +} diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/entity/SysEmployee.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/entity/SysEmployee.java new file mode 100644 index 000000000..0f537ebdb --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/entity/SysEmployee.java @@ -0,0 +1,57 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 企业员工表,用户-组织机构的关联 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_employee") +public class SysEmployee extends BaseEntity { + + /** + * 主键 + */ + @TableId("id") + private Long id; + + /** + * 用户id + */ + @TableField("user_id") + private Long userId; + + /** + * 所属机构id + */ + @TableField("organization_id") + private Long organizationId; + + /** + * 职位id集合,用逗号隔开 + */ + @TableField("position_ids") + private String positionIds; + + /** + * 是否是主要部门,Y-是,N-否,一个人只能有一个主要部门 + */ + @TableField("main_dept_flag") + private String mainDeptFlag; + + /** + * 员工编号 + */ + @TableField("employee_no") + private String employeeNo; + +} diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/entity/SysOrganization.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/entity/SysOrganization.java new file mode 100644 index 000000000..50ef27523 --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/entity/SysOrganization.java @@ -0,0 +1,77 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 系统组织机构表 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_organization") +public class SysOrganization extends BaseEntity { + + /** + * 主键 + */ + @TableId("id") + private Long id; + + /** + * 父id,顶级节点父id是0 + */ + @TableField("pid") + private Long pid; + + /** + * 父ids + */ + @TableField("pids") + private String pids; + + /** + * 组织名称 + */ + @TableField("name") + private String name; + + /** + * 组织编码 + */ + @TableField("code") + private String code; + + /** + * 排序 + */ + @TableField("sort") + private BigDecimal sort; + + /** + * 状态(1-启用,2-禁用) + */ + @TableField("status_flag") + private Integer statusFlag; + + /** + * 描述 + */ + @TableField("remark") + private String remark; + + /** + * 删除标记(Y-已删除,N-未删除) + */ + @TableField("del_flag") + private String delFlag; + +} diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/entity/SysPosition.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/entity/SysPosition.java new file mode 100644 index 000000000..cc13452c8 --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/entity/SysPosition.java @@ -0,0 +1,65 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 系统职位表 + * + * @author fengshuonan + * @date 2020/11/04 11:07 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_position") +public class SysPosition extends BaseEntity { + + /** + * 主键 + */ + @TableId("id") + private Long id; + + /** + * 职位名称 + */ + @TableField("name") + private String name; + + /** + * 职位编码 + */ + @TableField("code") + private String code; + + /** + * 排序 + */ + @TableField("sort") + private BigDecimal sort; + + /** + * 状态(1-启用,2-禁用) + */ + @TableField("status_flag") + private Integer statusFlag; + + /** + * 备注 + */ + @TableField("remark") + private String remark; + + /** + * 删除标记(Y-已删除,N-未删除) + */ + @TableField("del_flag") + private String delFlag; + +} diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/SysEmployeeMapper.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/SysEmployeeMapper.java new file mode 100644 index 000000000..f65871581 --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/SysEmployeeMapper.java @@ -0,0 +1,14 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.stylefeng.roses.kernel.system.modular.organization.entity.SysEmployee; + +/** + * 企业员工表,用户-组织机构的关联 Mapper 接口 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ +public interface SysEmployeeMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/SysOrganizationMapper.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/SysOrganizationMapper.java new file mode 100644 index 000000000..12a32899d --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/SysOrganizationMapper.java @@ -0,0 +1,14 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.stylefeng.roses.kernel.system.modular.organization.entity.SysOrganization; + +/** + * 系统组织机构表 Mapper 接口 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ +public interface SysOrganizationMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/SysPositionMapper.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/SysPositionMapper.java new file mode 100644 index 000000000..52fd5195b --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/SysPositionMapper.java @@ -0,0 +1,14 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.stylefeng.roses.kernel.system.modular.organization.entity.SysPosition; + +/** + * 系统职位表 Mapper 接口 + * + * @author fengshuonan + * @date 2020/11/04 11:07 + */ +public interface SysPositionMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/mapping/SysEmployeeMapper.xml b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/mapping/SysEmployeeMapper.xml new file mode 100644 index 000000000..ec716ad01 --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/mapping/SysEmployeeMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/mapping/SysOrganizationMapper.xml b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/mapping/SysOrganizationMapper.xml new file mode 100644 index 000000000..61c4141d6 --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/mapping/SysOrganizationMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/mapping/SysPositionMapper.xml b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/mapping/SysPositionMapper.xml new file mode 100644 index 000000000..ce88e3edc --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/mapper/mapping/SysPositionMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/DataScopeService.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/DataScopeService.java new file mode 100644 index 000000000..ea7f387ed --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/DataScopeService.java @@ -0,0 +1,122 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.service; + +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.auth.api.enums.DataScopeTypeEnum; +import cn.stylefeng.roses.kernel.db.api.DbOperatorApi; +import cn.stylefeng.roses.kernel.system.exception.DataScopeException; +import cn.stylefeng.roses.kernel.system.exception.enums.DataScopeExceptionEnum; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysEmployeeResponse; +import cn.stylefeng.roses.kernel.system.DataScopeApi; +import cn.stylefeng.roses.kernel.system.RoleServiceApi; +import cn.stylefeng.roses.kernel.system.SysEmployeeApi; +import cn.stylefeng.roses.kernel.system.UserServiceApi; +import cn.stylefeng.roses.kernel.system.pojo.organization.DataScopeResponse; +import cn.stylefeng.roses.kernel.system.pojo.role.response.SysRoleResponse; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 数据范围服务 + * + * @author fengshuonan + * @date 2020/11/6 12:01 + */ +@Service +public class DataScopeService implements DataScopeApi { + + @Resource + private UserServiceApi userServiceApi; + + @Resource + private RoleServiceApi roleServiceApi; + + @Resource + private SysEmployeeApi sysEmployeeApi; + + @Resource + private DbOperatorApi dbOperatorApi; + + @Override + public DataScopeResponse getDataScope(Long userId) { + + // 初始化返回结果 + DataScopeResponse dataScopeResponse = new DataScopeResponse(); + Set userIds = new HashSet<>(); + Set organizationIds = new HashSet<>(); + + if (userId == null) { + String userTip = StrUtil.format(DataScopeExceptionEnum.USER_ID_EMPTY_ERROR.getUserTip(), userId); + throw new DataScopeException(DataScopeExceptionEnum.USER_ID_EMPTY_ERROR, userTip); + } + + // 获取用户的所有角色 + List userRoleIdList = userServiceApi.getUserRoleIdList(userId); + if (userRoleIdList.isEmpty()) { + String userTip = StrUtil.format(DataScopeExceptionEnum.ROLE_EMPTY_ERROR.getUserTip(), userId); + throw new DataScopeException(DataScopeExceptionEnum.ROLE_EMPTY_ERROR, userTip); + } + + // 获取角色信息详情 + List sysRoles = roleServiceApi.getRolesByIds(userRoleIdList); + dataScopeResponse.setSysRoleResponses(sysRoles); + + // 获取角色中的数据范围类型 + Set dataScopeTypeEnums = sysRoles.stream().map(SysRoleResponse::getDataScopeTypeEnum).collect(Collectors.toSet()); + dataScopeResponse.setDataScopeTypeEnums(dataScopeTypeEnums); + + // 包含全部数据类型的,直接不用设置组织机构,直接放开 + if (dataScopeTypeEnums.contains(DataScopeTypeEnum.ALL)) { + return dataScopeResponse; + } + + // 包含指定部门数据,将指定数据范围增加到Set中 + if (dataScopeTypeEnums.contains(DataScopeTypeEnum.DEFINE)) { + + // 获取角色对应的组织机构范围 + List roleIds = sysRoles.stream().map(SysRoleResponse::getId).collect(Collectors.toList()); + List orgIds = roleServiceApi.getRoleDataScopes(roleIds); + organizationIds.addAll(orgIds); + } + + // 获取用户的主要部门信息 + SysEmployeeResponse userMainEmployee = sysEmployeeApi.getUserMainEmployee(userId); + + // 本部门和本部门以下,查出用户的主要部门,并且查询该部门本部门及以下的组织机构id列表 + if (dataScopeTypeEnums.contains(DataScopeTypeEnum.DEPT_WITH_CHILD)) { + + // 获取部门及以下部门的id列表 + Long organizationId = userMainEmployee.getOrganizationId(); + Set subOrgIds = dbOperatorApi.findSubListByParentId("sys_organization", "pids", "id", organizationId); + organizationIds.add(organizationId); + organizationIds.addAll(subOrgIds); + } + + // 如果是本部门,则查出本部门并添加到组织机构列表 + if (dataScopeTypeEnums.contains(DataScopeTypeEnum.DEPT)) { + + // 获取本部门的id + Long organizationId = userMainEmployee.getOrganizationId(); + organizationIds.add(organizationId); + } + + // 如果只是本用户数据,将用户本身装进userId数据范围 + if (dataScopeTypeEnums.contains(DataScopeTypeEnum.SELF)) { + userIds.add(userId); + } + + // 获取用户单独绑定的组织机构id + List userBindDataScope = userServiceApi.getUserBindDataScope(userId); + organizationIds.addAll(userBindDataScope); + + dataScopeResponse.setUserIds(userIds); + dataScopeResponse.setOrganizationIds(organizationIds); + + return dataScopeResponse; + } + +} diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/SysEmployeeService.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/SysEmployeeService.java new file mode 100644 index 000000000..7cb2c5b64 --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/SysEmployeeService.java @@ -0,0 +1,40 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.system.modular.organization.entity.SysEmployee; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysEmployeeRequest; + +import java.util.List; + +/** + * 企业员工的管理,用户和组织机构的绑定 + *

+ * 用户在添加到sys_user表后没有和组织机构关联,通过employee这个表来关联用户和组织机构 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ +public interface SysEmployeeService extends IService { + + /** + * 分页查询企业员工 + * + * @param sysEmployeeRequest 企业员工查询条件 + * @return 企业员工详情分页列表 + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + PageResult page(SysEmployeeRequest sysEmployeeRequest); + + /** + * 查询所有企业员工 + * + * @param sysEmployeeRequest 企业员工查询条件 + * @return 企业员工详情列表 + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + List list(SysEmployeeRequest sysEmployeeRequest); + +} \ No newline at end of file diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/SysOrganizationService.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/SysOrganizationService.java new file mode 100644 index 000000000..aef1ea958 --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/SysOrganizationService.java @@ -0,0 +1,106 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.service; + +import cn.stylefeng.roses.kernel.system.pojo.organization.SysOrganizationRequest; +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.rule.pojo.tree.DefaultTreeNode; +import cn.stylefeng.roses.kernel.system.modular.organization.entity.SysOrganization; + +import java.util.List; +import java.util.Set; + +/** + * 系统组织机构服务 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ +public interface SysOrganizationService extends IService { + + /** + * 添加系统组织机构 + * + * @param sysOrganizationRequest 组织机构请求参数 + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + void add(SysOrganizationRequest sysOrganizationRequest); + + /** + * 编辑系统组织机构 + * + * @param sysOrganizationRequest 组织机构请求参数 + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + void edit(SysOrganizationRequest sysOrganizationRequest); + + /** + * 删除系统组织机构 + * + * @param sysOrganizationRequest 组织机构请求参数 + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + void delete(SysOrganizationRequest sysOrganizationRequest); + + /** + * 修改组织机构状态 + * + * @param sysOrganizationRequest 请求参数 + * @author fengshuonan + * @date 2020/11/18 22:38 + */ + void updateStatus(SysOrganizationRequest sysOrganizationRequest); + + /** + * 查看详情系统组织机构 + * + * @param sysOrganizationRequest 组织机构请求参数 + * @return 组织机构详情 + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + SysOrganization detail(SysOrganizationRequest sysOrganizationRequest); + + /** + * 分页查询系统组织机构 + * + * @param sysOrganizationRequest 组织机构请求参数 + * @return 组织机构详情分页列表 + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + PageResult page(SysOrganizationRequest sysOrganizationRequest); + + /** + * 查询所有系统组织机构 + * + * @param sysOrganizationRequest 组织机构请求参数 + * @return 组织机构详情列表 + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + List list(SysOrganizationRequest sysOrganizationRequest); + + /** + * 获取组织架构树 + * + * @param sysOrganizationRequest 查询参数 + * @return 系统组织机构树 + * @author fengshuonan + * @date 2020/11/6 13:41 + */ + List tree(SysOrganizationRequest sysOrganizationRequest); + + /** + * 查询所有参数组织架构id集合的所有层级的父id,包含父级的父级等 + * + * @param organizationIds 组织架构id集合 + * @return 被查询参数id集合的所有层级父级id,包含他们本身 + * @author fengshuonan + * @date 2020/11/6 14:24 + */ + Set findAllLevelParentIdsByOrganizations(Set organizationIds); + +} \ No newline at end of file diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/SysPositionService.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/SysPositionService.java new file mode 100644 index 000000000..5060fe22d --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/SysPositionService.java @@ -0,0 +1,94 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.service; + +import cn.stylefeng.roses.kernel.system.pojo.organization.SysPositionRequest; +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.system.modular.organization.entity.SysPosition; + +import java.util.List; + +/** + * 职位信息服务 + * + * @author fengshuonan + * @date 2020/11/04 11:07 + */ +public interface SysPositionService extends IService { + + /** + * 添加职位 + * + * @param sysPositionRequest 请求参数 + * @author fengshuonan + * @date 2020/11/04 11:07 + */ + void add(SysPositionRequest sysPositionRequest); + + /** + * 编辑职位 + * + * @param sysPositionRequest 请求参数 + * @author fengshuonan + * @date 2020/11/04 11:07 + */ + void edit(SysPositionRequest sysPositionRequest); + + /** + * 删除职位 + * + * @param sysPositionRequest 请求参数 + * @author fengshuonan + * @date 2020/11/04 11:07 + */ + void delete(SysPositionRequest sysPositionRequest); + + /** + * 更新装填 + * + * @param sysPositionRequest 请求参数 + * @author fengshuonan + * @date 2020/11/18 23:00 + */ + void updateStatus(SysPositionRequest sysPositionRequest); + + /** + * 查看详情职位 + * + * @param sysPositionRequest 请求参数 + * @return 职位详情 + * @author fengshuonan + * @date 2020/11/04 11:07 + */ + SysPosition detail(SysPositionRequest sysPositionRequest); + + /** + * 分页查询职位 + * + * @param sysPositionRequest 请求参数 + * @return 职位详情分页列表 + * @author fengshuonan + * @date 2020/11/04 11:07 + */ + PageResult page(SysPositionRequest sysPositionRequest); + + /** + * 查询所有职位 + * + * @param sysPositionRequest 请求参数 + * @return 职位详情列表 + * @author fengshuonan + * @date 2020/11/04 11:07 + */ + List list(SysPositionRequest sysPositionRequest); + + /** + * 通过职位id列表,获取对应的名称列表 + * + * @param positionIds 职位id列表 + * @return 职位名称列表 + * @author fengshuonan + * @date 2020/11/19 23:22 + */ + List getPositionNamesByPositionIds(List positionIds); + +} \ No newline at end of file diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/impl/SysEmployeeServiceImpl.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/impl/SysEmployeeServiceImpl.java new file mode 100644 index 000000000..81a363aaf --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/impl/SysEmployeeServiceImpl.java @@ -0,0 +1,212 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.system.exception.SystemModularException; +import cn.stylefeng.roses.kernel.system.exception.enums.EmployeeExceptionEnum; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import cn.stylefeng.roses.kernel.system.SysEmployeeApi; +import cn.stylefeng.roses.kernel.system.modular.organization.entity.SysEmployee; +import cn.stylefeng.roses.kernel.system.modular.organization.entity.SysOrganization; +import cn.stylefeng.roses.kernel.system.modular.organization.mapper.SysEmployeeMapper; +import cn.stylefeng.roses.kernel.system.modular.organization.service.SysEmployeeService; +import cn.stylefeng.roses.kernel.system.modular.organization.service.SysOrganizationService; +import cn.stylefeng.roses.kernel.system.modular.organization.service.SysPositionService; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysEmployeeRequest; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysEmployeeResponse; +import org.springframework.aop.framework.AopContext; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 企业员工表,用户-组织机构的关联 服务实现类 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ +@Service +public class SysEmployeeServiceImpl extends ServiceImpl implements SysEmployeeService, SysEmployeeApi { + + @Resource + private SysOrganizationService sysOrganizationService; + + @Resource + private SysPositionService sysPositionService; + + @Override + public PageResult page(SysEmployeeRequest sysEmployeeRequest) { + + // 构造条件 + LambdaQueryWrapper queryWrapper = createWrapper(sysEmployeeRequest); + + // 查询分页结果 + return PageResultFactory.createPageResult(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public List list(SysEmployeeRequest sysEmployeeRequest) { + + // 构造条件 + LambdaQueryWrapper queryWrapper = createWrapper(sysEmployeeRequest); + + return this.list(queryWrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateEmployee(Long userId, List sysEmployeeRequest) { + + // 删除用户对应的所有员工关联信息 + SysEmployeeServiceImpl sysEmployeeService = (SysEmployeeServiceImpl) AopContext.currentProxy(); + sysEmployeeService.deleteEmployeeByUserId(userId); + + // 添加用户的组织架构关联 + ArrayList sysEmployees = new ArrayList<>(); + for (SysEmployeeRequest employeeRequest : sysEmployeeRequest) { + SysEmployee sysEmployee = new SysEmployee(); + BeanUtil.copyProperties(employeeRequest, sysEmployee); + sysEmployees.add(sysEmployee); + } + + // 批量添加雇员信息 + sysEmployeeService.saveBatch(sysEmployees); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteEmployeeByUserId(Long userId) { + + // 删除用户id对应的员工信息 + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(SysEmployee::getUserId, userId); + + this.remove(updateWrapper); + } + + @Override + public SysEmployeeResponse getUserMainEmployee(Long userId) { + + // 组装用户主部门员工信息查询条件 + SysEmployeeRequest sysEmployeeRequest = new SysEmployeeRequest(); + sysEmployeeRequest.setUserId(userId); + sysEmployeeRequest.setMainDeptFlag(YesOrNotEnum.Y.getCode()); + + LambdaQueryWrapper wrapper = createWrapper(sysEmployeeRequest); + List sysEmployees = this.list(wrapper); + + // 查询不到,提示企业员工信息不存在 + if (sysEmployees == null || sysEmployees.isEmpty()) { + String userTip = StrUtil.format(EmployeeExceptionEnum.EMPLOYEE_NOT_FOUND.getUserTip(), userId); + throw new SystemModularException(EmployeeExceptionEnum.EMPLOYEE_NOT_FOUND, userTip); + } + + // 查询出多个主部门,提示该用户异常 + if (sysEmployees.size() > 1) { + String userTip = StrUtil.format(EmployeeExceptionEnum.EMPLOYEE_MANY_MAIN_NOT_FOUND.getUserTip(), userId); + throw new SystemModularException(EmployeeExceptionEnum.EMPLOYEE_MANY_MAIN_NOT_FOUND, userTip); + } + + return parseSysEmployee(sysEmployees.get(0)); + } + + @Override + public List getUserAllEmployee(Long userId) { + + // 组装用户主部门员工信息查询条件 + SysEmployeeRequest sysEmployeeRequest = new SysEmployeeRequest(); + sysEmployeeRequest.setUserId(userId); + + LambdaQueryWrapper wrapper = createWrapper(sysEmployeeRequest); + List sysEmployees = this.list(wrapper); + + // 查询不到员工信息报异常 + if (sysEmployees.isEmpty()) { + String userTip = StrUtil.format(EmployeeExceptionEnum.EMPLOYEE_NOT_FOUND.getUserTip(), userId); + throw new SystemModularException(EmployeeExceptionEnum.EMPLOYEE_NOT_FOUND, userTip); + } + + // 转化为SysEmployeeResponse对象,拼接组织机构名称和职位名称 + ArrayList sysEmployeeResponses = new ArrayList<>(); + for (SysEmployee sysEmployee : sysEmployees) { + sysEmployeeResponses.add(parseSysEmployee(sysEmployee)); + } + + return sysEmployeeResponses; + } + + + /** + * 企业雇员的查询条件 + * + * @author fengshuonan + * @date 2020/11/6 16:37 + */ + private LambdaQueryWrapper createWrapper(SysEmployeeRequest sysEmployeeRequest) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysEmployeeRequest)) { + + // 拼接用户id + if (ObjectUtil.isNotEmpty(sysEmployeeRequest.getUserId())) { + queryWrapper.eq(SysEmployee::getUserId, sysEmployeeRequest.getUserId()); + } + + // 拼接组织机构id + if (ObjectUtil.isNotEmpty(sysEmployeeRequest.getOrganizationId())) { + queryWrapper.eq(SysEmployee::getOrganizationId, sysEmployeeRequest.getOrganizationId()); + } + + // 拼接职位id查询条件 + if (ObjectUtil.isNotEmpty(sysEmployeeRequest.getPositionId())) { + queryWrapper.like(SysEmployee::getPositionIds, sysEmployeeRequest.getPositionId()); + } + + // 拼接主部门标识 + if (ObjectUtil.isNotEmpty(sysEmployeeRequest.getMainDeptFlag())) { + queryWrapper.eq(SysEmployee::getMainDeptFlag, sysEmployeeRequest.getMainDeptFlag()); + } + + } + return queryWrapper; + } + + /** + * 转化实体类为Response对象,填充机构名称和职位名称 + * + * @author fengshuonan + * @date 2020/11/20 22:44 + */ + private SysEmployeeResponse parseSysEmployee(SysEmployee sysEmployee) { + + SysEmployeeResponse sysEmployeeResponse = new SysEmployeeResponse(); + BeanUtil.copyProperties(sysEmployee, sysEmployeeResponse); + + // 组装组织机构名称 + SysOrganization sysOrganization = sysOrganizationService.getById(sysEmployee.getOrganizationId()); + if (sysOrganization != null) { + sysEmployeeResponse.setOrganizationName(sysOrganization.getName()); + } + + // 组装职位名称,用逗号隔开多个 + String[] positionIdsString = StrUtil.split(sysEmployee.getPositionIds(), StrUtil.COMMA); + List positionIds = Arrays.stream(positionIdsString).map(Long::valueOf).collect(Collectors.toList()); + List positionNamesByPositionIds = sysPositionService.getPositionNamesByPositionIds(positionIds); + String join = StrUtil.join(StrUtil.COMMA, positionNamesByPositionIds); + sysEmployeeResponse.setPositionNames(join); + + return sysEmployeeResponse; + } + +} \ No newline at end of file diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/impl/SysOrganizationServiceImpl.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/impl/SysOrganizationServiceImpl.java new file mode 100644 index 000000000..a843815ac --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/impl/SysOrganizationServiceImpl.java @@ -0,0 +1,352 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.system.constants.SystemConstants; +import cn.stylefeng.roses.kernel.system.exception.SystemModularException; +import cn.stylefeng.roses.kernel.system.exception.enums.DataScopeExceptionEnum; +import cn.stylefeng.roses.kernel.system.exception.enums.OrganizationExceptionEnum; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.auth.api.context.LoginContext; +import cn.stylefeng.roses.kernel.auth.api.enums.DataScopeTypeEnum; +import cn.stylefeng.roses.kernel.db.api.context.DbOperatorContext; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.rule.enums.StatusEnum; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import cn.stylefeng.roses.kernel.rule.factory.DefaultTreeBuildFactory; +import cn.stylefeng.roses.kernel.rule.pojo.tree.DefaultTreeNode; +import cn.stylefeng.roses.kernel.system.RoleServiceApi; +import cn.stylefeng.roses.kernel.system.UserServiceApi; +import cn.stylefeng.roses.kernel.system.modular.organization.entity.SysEmployee; +import cn.stylefeng.roses.kernel.system.modular.organization.entity.SysOrganization; +import cn.stylefeng.roses.kernel.system.modular.organization.mapper.SysOrganizationMapper; +import cn.stylefeng.roses.kernel.system.modular.organization.service.SysEmployeeService; +import cn.stylefeng.roses.kernel.system.modular.organization.service.SysOrganizationService; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysEmployeeRequest; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysOrganizationRequest; +import cn.stylefeng.roses.kernel.system.util.DataScopeUtil; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 组织架构管理 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ +@Service +public class SysOrganizationServiceImpl extends ServiceImpl implements SysOrganizationService { + + @Resource + private SysEmployeeService sysEmployeeService; + + @Resource + private RoleServiceApi roleServiceApi; + + @Resource + private UserServiceApi userServiceApi; + + @Override + public void add(SysOrganizationRequest sysOrganizationRequest) { + + // 获取父id + Long pid = sysOrganizationRequest.getPid(); + + // 校验数据范围 + if (DataScopeUtil.validateDataScopeByOrganizationId(pid)) { + String userTip = StrUtil.format(DataScopeExceptionEnum.DATA_SCOPE_ERROR.getUserTip(), DataScopeUtil.getDataScopeTip()); + throw new SystemModularException(DataScopeExceptionEnum.DATA_SCOPE_ERROR, userTip); + } + + SysOrganization sysOrganization = new SysOrganization(); + BeanUtil.copyProperties(sysOrganizationRequest, sysOrganization); + + // 填充parentIds + this.fillParentIds(sysOrganization); + + // 设置状态为启用,未删除状态 + sysOrganization.setStatusFlag(StatusEnum.ENABLE.getCode()); + + this.save(sysOrganization); + } + + @Override + public void edit(SysOrganizationRequest sysOrganizationRequest) { + + SysOrganization sysOrganization = this.querySysOrganization(sysOrganizationRequest); + Long id = sysOrganization.getId(); + + // 校验数据范围 + if (DataScopeUtil.validateDataScopeByOrganizationId(id)) { + String userTip = StrUtil.format(DataScopeExceptionEnum.DATA_SCOPE_ERROR.getUserTip(), DataScopeUtil.getDataScopeTip()); + throw new SystemModularException(DataScopeExceptionEnum.DATA_SCOPE_ERROR, userTip); + } + + BeanUtil.copyProperties(sysOrganizationRequest, sysOrganization); + + // 填充parentIds + this.fillParentIds(sysOrganization); + + // 不能修改状态,用修改状态接口修改状态 + sysOrganization.setStatusFlag(null); + + // 更新这条记录 + this.updateById(sysOrganization); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(SysOrganizationRequest sysOrganizationRequest) { + + SysOrganization sysOrganization = this.querySysOrganization(sysOrganizationRequest); + Long organizationId = sysOrganization.getId(); + + // 校验数据范围 + if (DataScopeUtil.validateDataScopeByOrganizationId(organizationId)) { + String userTip = StrUtil.format(DataScopeExceptionEnum.DATA_SCOPE_ERROR.getUserTip(), DataScopeUtil.getDataScopeTip()); + throw new SystemModularException(DataScopeExceptionEnum.DATA_SCOPE_ERROR, userTip); + } + + // 该机构下有员工,则不能删 + SysEmployeeRequest sysEmployeeRequest = new SysEmployeeRequest(); + sysEmployeeRequest.setOrganizationId(organizationId); + List sysEmployees = sysEmployeeService.list(sysEmployeeRequest); + if (sysEmployees != null && !sysEmployees.isEmpty()) { + throw new SystemModularException(OrganizationExceptionEnum.DELETE_ORGANIZATION_ERROR); + } + + // 级联删除子节点,逻辑删除 + Set childIdList = DbOperatorContext.me().findSubListByParentId("sys_organization", "pids", "id", organizationId); + childIdList.add(organizationId); + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.in(SysOrganization::getId, childIdList) + .set(SysOrganization::getDelFlag, YesOrNotEnum.Y.getCode()); + this.update(updateWrapper); + + // 删除角色对应的组织架构数据范围 + roleServiceApi.deleteRoleDataScopeListByOrgIdList(childIdList); + + // 删除用户对应的组织架构数据范围 + userServiceApi.deleteUserDataScopeListByOrgIdList(childIdList); + } + + @Override + public void updateStatus(SysOrganizationRequest sysOrganizationRequest) { + + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(SysOrganization::getId, sysOrganizationRequest.getId()); + updateWrapper.set(SysOrganization::getStatusFlag, sysOrganizationRequest.getStatusFlag()); + + this.update(updateWrapper); + } + + @Override + public SysOrganization detail(SysOrganizationRequest sysOrganizationRequest) { + return this.querySysOrganization(sysOrganizationRequest); + } + + @Override + public PageResult page(SysOrganizationRequest sysOrganizationRequest) { + + // 构造条件 + LambdaQueryWrapper wrapper = createWrapper(sysOrganizationRequest); + + // 获取分页参数 + Page page = PageFactory.defaultPage(); + + // 返回分页结果 + return PageResultFactory.createPageResult(this.page(page, wrapper)); + } + + @Override + public List list(SysOrganizationRequest sysOrganizationRequest) { + + // 构造条件 + LambdaQueryWrapper wrapper = createWrapper(sysOrganizationRequest); + + return this.list(wrapper); + } + + @Override + public List tree(SysOrganizationRequest sysOrganizationRequest) { + + // 定义返回结果 + List treeNodeList = CollectionUtil.newArrayList(); + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 如果是超级管理员 或 数据范围是所有,则不过滤数据范围 + boolean needToDataScope = true; + if (LoginContext.me().getSuperAdminFlag()) { + Set dataScopeTypes = LoginContext.me().getLoginUser().getDataScopeTypes(); + if (dataScopeTypes != null && dataScopeTypes.contains(DataScopeTypeEnum.ALL)) { + needToDataScope = false; + } + } + + // 如果需要数据范围过滤,则获取用户的数据范围,拼接查询条件 + if (needToDataScope) { + Set dataScope = LoginContext.me().getLoginUser().getOrganizationIdDataScope(); + + // 数据范围没有,直接返回空 + if (ObjectUtil.isEmpty(dataScope)) { + return treeNodeList; + } + + // 根据组织机构数据范围的上级组织,用于展示完整的树形结构 + Set allLevelParentIdsByOrganizations = this.findAllLevelParentIdsByOrganizations(dataScope); + + // 拼接查询条件 + queryWrapper.in(SysOrganization::getId, allLevelParentIdsByOrganizations); + } + + // 只查询未删除的 + queryWrapper.eq(SysOrganization::getDelFlag, YesOrNotEnum.N.getCode()); + + // 根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysOrganization::getSort); + + // 组装节点 + List list = this.list(queryWrapper); + for (SysOrganization sysOrganization : list) { + DefaultTreeNode orgTreeNode = new DefaultTreeNode(); + orgTreeNode.setId(String.valueOf(sysOrganization.getId())); + orgTreeNode.setPId(String.valueOf(sysOrganization.getPid())); + orgTreeNode.setName(sysOrganization.getName()); + orgTreeNode.setSort(sysOrganization.getSort()); + treeNodeList.add(orgTreeNode); + } + + // 构建树并返回 + return new DefaultTreeBuildFactory().doTreeBuild(treeNodeList); + } + + @Override + public Set findAllLevelParentIdsByOrganizations(Set organizationIds) { + + // 定义返回结果 + Set allLevelParentIds = new HashSet<>(organizationIds); + + // 查询出这些节点的pids字段 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysOrganization::getId, organizationIds); + queryWrapper.select(SysOrganization::getPids); + + List organizationList = this.list(queryWrapper); + if (organizationList == null || organizationList.isEmpty()) { + return allLevelParentIds; + } + + // 把所有的pids分割,并放入到set中 + for (SysOrganization sysOrganization : organizationList) { + + // 获取pids值 + String pids = sysOrganization.getPids(); + + // 去掉所有的左中括号 + String cutLeft = StrUtil.removeAll(pids, SystemConstants.PID_LEFT_DIVIDE_SYMBOL); + + // 去掉所有的右中括号 + String cutRight = StrUtil.removeAll(cutLeft, SystemConstants.PID_RIGHT_DIVIDE_SYMBOL); + + // 按逗号分割这个字符串,得到pid的数组 + String[] finalPidArray = cutRight.split(StrUtil.COMMA); + + // 遍历这些值,放入到最终的set + for (String pid : finalPidArray) { + allLevelParentIds.add(Convert.toLong(pid)); + } + } + + return allLevelParentIds; + } + + /** + * 创建组织架构的通用条件查询wrapper + * + * @author fengshuonan + * @date 2020/11/6 10:16 + */ + private LambdaQueryWrapper createWrapper(SysOrganizationRequest sysOrganizationRequest) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysOrganizationRequest)) { + + // 拼接机构名称查询条件 + if (ObjectUtil.isNotEmpty(sysOrganizationRequest.getName())) { + queryWrapper.like(SysOrganization::getName, sysOrganizationRequest.getName()); + } + + // 拼接机构id查询条件 + if (ObjectUtil.isNotEmpty(sysOrganizationRequest.getId())) { + queryWrapper.eq(SysOrganization::getId, sysOrganizationRequest.getId()); + } + + // 拼接父机构id查询条件 + if (ObjectUtil.isNotEmpty(sysOrganizationRequest.getPid())) { + queryWrapper + .eq(SysOrganization::getId, sysOrganizationRequest.getPid()) + .or() + .like(SysOrganization::getPids, sysOrganizationRequest.getPid()); + } + } + + // 查询未删除状态的 + queryWrapper.eq(SysOrganization::getDelFlag, YesOrNotEnum.N.getCode()); + + // 根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysOrganization::getSort); + + return queryWrapper; + } + + + /** + * 获取系统组织机构 + * + * @author fengshuonan + * @date 2020/11/04 11:05 + */ + private SysOrganization querySysOrganization(SysOrganizationRequest sysOrganizationRequest) { + SysOrganization sysorganization = this.getById(sysOrganizationRequest.getId()); + if (ObjectUtil.isEmpty(sysorganization)) { + throw new SystemModularException(OrganizationExceptionEnum.CANT_FIND_ORG); + } + return sysorganization; + } + + /** + * 填充该节点的pIds + * + * @author fengshuonan + * @date 2020/11/5 13:45 + */ + private void fillParentIds(SysOrganization sysOrganization) { + + // 如果是一级节点(一级节点的pid是0) + if (sysOrganization.getPid().equals(SystemConstants.DEFAULT_PARENT_ID)) { + // 设置一级节点的pid为[0], + sysOrganization.setPids(SystemConstants.PID_LEFT_DIVIDE_SYMBOL + SystemConstants.DEFAULT_PARENT_ID + SystemConstants.PID_RIGHT_DIVIDE_SYMBOL + ","); + } else { + // 获取父组织机构 + SysOrganization parentSysOrganization = this.getById(sysOrganization.getPid()); + + // 设置本节点的父ids为 (上一个节点的pids + (上级节点的id) ) + sysOrganization.setPids( + parentSysOrganization.getPids() + SystemConstants.PID_LEFT_DIVIDE_SYMBOL + parentSysOrganization.getId() + SystemConstants.PID_RIGHT_DIVIDE_SYMBOL + ","); + } + } + +} \ No newline at end of file diff --git a/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/impl/SysPositionServiceImpl.java b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/impl/SysPositionServiceImpl.java new file mode 100644 index 000000000..ecd29ad6f --- /dev/null +++ b/kernel-s-system/system-business-organization/src/main/java/cn/stylefeng/roses/kernel/system/modular/organization/service/impl/SysPositionServiceImpl.java @@ -0,0 +1,176 @@ +package cn.stylefeng.roses.kernel.system.modular.organization.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.stylefeng.roses.kernel.system.exception.SystemModularException; +import cn.stylefeng.roses.kernel.system.exception.enums.PositionExceptionEnum; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.rule.enums.StatusEnum; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import cn.stylefeng.roses.kernel.system.modular.organization.entity.SysEmployee; +import cn.stylefeng.roses.kernel.system.modular.organization.entity.SysPosition; +import cn.stylefeng.roses.kernel.system.modular.organization.mapper.SysPositionMapper; +import cn.stylefeng.roses.kernel.system.modular.organization.service.SysEmployeeService; +import cn.stylefeng.roses.kernel.system.modular.organization.service.SysPositionService; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysEmployeeRequest; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysPositionRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 系统职位表 服务实现类 + * + * @author fengshuonan + * @date 2020/11/04 11:07 + */ +@Service +public class SysPositionServiceImpl extends ServiceImpl implements SysPositionService { + + @Resource + private SysEmployeeService sysEmployeeService; + + @Override + public void add(SysPositionRequest sysPositionRequest) { + SysPosition sysPosition = new SysPosition(); + BeanUtil.copyProperties(sysPositionRequest, sysPosition); + + // 设置状态为启用 + sysPosition.setStatusFlag(StatusEnum.ENABLE.getCode()); + + this.save(sysPosition); + } + + @Override + public void edit(SysPositionRequest sysPositionRequest) { + + SysPosition sysPosition = this.querySysPosition(sysPositionRequest); + BeanUtil.copyProperties(sysPositionRequest, sysPosition); + + // 不能修改状态,用修改状态接口修改状态 + sysPosition.setStatusFlag(null); + + this.updateById(sysPosition); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(SysPositionRequest sysPositionRequest) { + + SysPosition sysPosition = this.querySysPosition(sysPositionRequest); + + // 该职位下是否有员工 + SysEmployeeRequest sysEmployeeRequest = new SysEmployeeRequest(); + sysEmployeeRequest.setPositionId(sysPosition.getId()); + List haveEmployee = sysEmployeeService.list(sysEmployeeRequest); + + // 职位有绑定员工,不能删除 + if (!haveEmployee.isEmpty()) { + throw new SystemModularException(PositionExceptionEnum.CANT_DELETE_POSITION); + } + + // 逻辑删除 + sysPosition.setDelFlag(YesOrNotEnum.Y.getCode()); + + this.updateById(sysPosition); + } + + @Override + public void updateStatus(SysPositionRequest sysPositionRequest) { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(SysPosition::getId, sysPositionRequest.getId()); + updateWrapper.set(SysPosition::getStatusFlag, sysPositionRequest.getStatusFlag()); + + this.update(updateWrapper); + } + + @Override + public SysPosition detail(SysPositionRequest sysPositionRequest) { + return this.querySysPosition(sysPositionRequest); + } + + @Override + public PageResult page(SysPositionRequest sysPositionRequest) { + LambdaQueryWrapper wrapper = createWrapper(sysPositionRequest); + + Page page = this.page(PageFactory.defaultPage(), wrapper); + return PageResultFactory.createPageResult(page); + } + + @Override + public List list(SysPositionRequest sysPositionRequest) { + LambdaQueryWrapper wrapper = createWrapper(sysPositionRequest); + return this.list(wrapper); + } + + @Override + public List getPositionNamesByPositionIds(List positionIds) { + + // 拼接查询条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysPosition::getId, positionIds); + queryWrapper.select(SysPosition::getName); + + // 查询结果 + List sysPositions = this.list(queryWrapper); + + // 把name组装起来 + return sysPositions.stream().map(SysPosition::getName).collect(Collectors.toList()); + } + + /** + * 拼接查询条件 + * + * @author fengshuonan + * @date 2020/11/6 18:35 + */ + private LambdaQueryWrapper createWrapper(SysPositionRequest sysPositionRequest) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysPositionRequest)) { + + // 拼接职位名称条件 + if (ObjectUtil.isNotEmpty(sysPositionRequest.getName())) { + queryWrapper.like(SysPosition::getName, sysPositionRequest.getName()); + } + + // 拼接职位编码条件 + if (ObjectUtil.isNotEmpty(sysPositionRequest.getCode())) { + queryWrapper.eq(SysPosition::getCode, sysPositionRequest.getCode()); + } + + } + + // 查询未删除状态的 + queryWrapper.eq(SysPosition::getDelFlag, YesOrNotEnum.N.getCode()); + + // 根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysPosition::getSort); + + return queryWrapper; + } + + /** + * 获取系统职位表 + * + * @author fengshuonan + * @date 2020/11/18 22:59 + */ + private SysPosition querySysPosition(SysPositionRequest sysPositionRequest) { + SysPosition sysposition = this.getById(sysPositionRequest.getId()); + if (ObjectUtil.isEmpty(sysposition)) { + String userTip = StrUtil.format(PositionExceptionEnum.CANT_FIND_POSITION.getUserTip(), sysposition.getId()); + throw new SystemModularException(PositionExceptionEnum.CANT_FIND_POSITION, userTip); + } + return sysposition; + } +} \ No newline at end of file diff --git a/kernel-s-system/system-business-resource/README.md b/kernel-s-system/system-business-resource/README.md new file mode 100644 index 000000000..df9b8ae27 --- /dev/null +++ b/kernel-s-system/system-business-resource/README.md @@ -0,0 +1 @@ +资源模块的业务模块,提供对资源的在线管理,搜集,持久化等操作 \ No newline at end of file diff --git a/kernel-s-system/system-business-resource/pom.xml b/kernel-s-system/system-business-resource/pom.xml new file mode 100644 index 000000000..5ee89443e --- /dev/null +++ b/kernel-s-system/system-business-resource/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-system + 1.0.0 + ../pom.xml + + + system-business-resource + + jar + + + + + + cn.stylefeng.roses + system-api + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + + cn.stylefeng.roses + cache-api + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/cache/ResourceCache.java b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/cache/ResourceCache.java new file mode 100644 index 000000000..c5a964a27 --- /dev/null +++ b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/cache/ResourceCache.java @@ -0,0 +1,49 @@ +package cn.stylefeng.roses.kernel.resource.modular.cache; + +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceDefinition; +import cn.stylefeng.roses.kernel.resource.modular.entity.SysResource; +import cn.stylefeng.roses.kernel.resource.modular.factory.ResourceFactory; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * 资源缓存 + * + * @author fengshuonan + * @date 2019-09-10-17:29 + */ +@Component +public class ResourceCache { + + private final List resourceDefinitions = new ArrayList<>(); + + /** + * 保存资源存储到缓存 + * + * @author fengshuonan + * @date 2020/11/24 20:06 + */ + public void saveResourcesToCache(List sysResources) { + if (sysResources == null || sysResources.size() == 0) { + return; + } + + for (SysResource sysResource : sysResources) { + ResourceDefinition resourceDefinition = ResourceFactory.createResourceDefinition(sysResource); + resourceDefinitions.add(resourceDefinition); + } + } + + /** + * 获取缓存的所有资源 + * + * @author fengshuonan + * @date 2020/12/9 14:22 + */ + public List getAllCaches() { + return resourceDefinitions; + } + +} diff --git a/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/controller/ResourceController.java b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/controller/ResourceController.java new file mode 100644 index 000000000..303f2ada0 --- /dev/null +++ b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/controller/ResourceController.java @@ -0,0 +1,53 @@ +package cn.stylefeng.roses.kernel.resource.modular.controller; + +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.modular.entity.SysResource; +import cn.stylefeng.roses.kernel.resource.modular.service.SysResourceService; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import cn.stylefeng.roses.kernel.system.pojo.resource.request.ResourceRequest; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 资源管理控制器 + * + * @author fengshuonan + * @date 2020/11/24 19:47 + */ +@RestController +@ApiResource(name = "资源管理") +public class ResourceController { + + @Autowired + private SysResourceService sysResourceService; + + /** + * 获取资源列表 + * + * @author fengshuonan + * @date 2020/11/24 19:47 + */ + @GetResource(name = "获取资源列表", path = "/resource/pageList") + public ResponseData pageList(ResourceRequest resourceRequest) { + Page result = this.sysResourceService.getResourceList(resourceRequest); + return new SuccessResponseData(result); + } + + /** + * 获取资源下拉列表(获取菜单资源) + * + * @author fengshuonan + * @date 2020/11/24 19:51 + */ + @GetResource(name = "获取资源下拉列表", path = "/resource/getMenuResourceList") + public ResponseData getMenuResourceList(ResourceRequest resourceRequest) { + List menuResourceList = this.sysResourceService.getMenuResourceList(resourceRequest); + return new SuccessResponseData(menuResourceList); + } + +} diff --git a/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/entity/SysResource.java b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/entity/SysResource.java new file mode 100644 index 000000000..04f6e0e4b --- /dev/null +++ b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/entity/SysResource.java @@ -0,0 +1,140 @@ +package cn.stylefeng.roses.kernel.resource.modular.entity; + +import cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 资源表 + * + * @author fengshuonan + * @date 2020/11/23 22:45 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_resource") +public class SysResource extends BaseEntity { + + /** + * 资源id + */ + @TableId("id") + private String id; + + /** + * 应用编码 + */ + @TableField("app_code") + private String appCode; + + /** + * 资源编码 + */ + @TableField("code") + private String code; + + /** + * 资源名称 + */ + @TableField("name") + private String name; + + /** + * 项目编码 + */ + @TableField("project_code") + private String projectCode; + + /** + * 类名称 + */ + @TableField("class_name") + private String className; + + /** + * 方法名称 + */ + @TableField("method_name") + private String methodName; + + /** + * 资源模块编码 + */ + @TableField("modular_code") + private String modularCode; + + /** + * 资源模块名称 + */ + @TableField("modular_name") + private String modularName; + + /** + * 资源初始化的服务器ip地址 + */ + @TableField("ip_address") + private String ipAddress; + + /** + * 资源url + */ + @TableField("url") + private String url; + + /** + * http请求方法 + */ + @TableField("http_method") + private String httpMethod; + + /** + * 是否是方法(Y-页面,N-API接口) + */ + @TableField("menu_flag") + private String menuFlag; + + /** + * 是否需要登录(Y-是,N-否) + */ + @TableField("required_login_flag") + private String requiredLoginFlag; + + /** + * 是否需要鉴权(Y-是,N-否) + */ + @TableField("required_permission_flag") + private String requiredPermissionFlag; + + /** + * 需要进行参数校验的分组 + *

+ * json形式存储 + */ + @TableField("validate_groups") + private String validateGroups; + + /** + * 接口参数的字段描述 + *

+ * json形式存储 + */ + @TableField("param_field_descriptions") + private String paramFieldDescriptions; + + /** + * 接口返回结果的字段描述 + *

+ * json形式存储 + */ + @TableField("response_field_descriptions") + private String responseFieldDescriptions; + + /** + * 应用名称 + */ + private transient String appName; + +} diff --git a/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/factory/ResourceFactory.java b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/factory/ResourceFactory.java new file mode 100644 index 000000000..679238cb1 --- /dev/null +++ b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/factory/ResourceFactory.java @@ -0,0 +1,108 @@ +package cn.stylefeng.roses.kernel.resource.modular.factory; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.util.ObjectUtil; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceDefinition; +import cn.stylefeng.roses.kernel.resource.modular.entity.SysResource; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.parser.Feature; +import com.alibaba.fastjson.serializer.SerializerFeature; +import org.springframework.beans.BeanUtils; + +import java.util.Set; + +/** + * ResourceDefinition和SysResource实体互转 + * + * @author fengshuonan + * @date 2019-05-29-14:37 + */ +public class ResourceFactory { + + /** + * ResourceDefinition转化为SysResource实体 + * + * @author fengshuonan + * @date 2020/12/9 14:14 + */ + public static SysResource createResource(ResourceDefinition resourceDefinition) { + SysResource resource = new SysResource(); + BeanUtils.copyProperties(resourceDefinition, resource); + resource.setId(resourceDefinition.getCode()); + + if (resourceDefinition.getMenuFlag()) { + resource.setMenuFlag(YesOrNotEnum.Y.name()); + } else { + resource.setMenuFlag(YesOrNotEnum.N.name()); + } + + if (resourceDefinition.getRequiredLogin()) { + resource.setRequiredLoginFlag(YesOrNotEnum.Y.name()); + } else { + resource.setRequiredLoginFlag(YesOrNotEnum.N.name()); + } + + if (resourceDefinition.getRequiredPermission()) { + resource.setRequiredPermissionFlag(YesOrNotEnum.Y.name()); + } else { + resource.setRequiredPermissionFlag(YesOrNotEnum.N.name()); + } + + // 转化校验组 + if (ObjectUtil.isNotEmpty(resourceDefinition.getValidateGroups())) { + resource.setValidateGroups(JSON.toJSONString(resourceDefinition.getValidateGroups(), SerializerFeature.WriteClassName)); + } + + // 转化接口参数的字段描述 + if (ObjectUtil.isNotEmpty(resourceDefinition.getParamFieldDescriptions())) { + resource.setParamFieldDescriptions(JSON.toJSONString(resourceDefinition.getParamFieldDescriptions(), SerializerFeature.WriteClassName)); + } + + // 转化接口返回结果的字段描述 + if (ObjectUtil.isNotEmpty(resourceDefinition.getResponseFieldDescriptions())) { + resource.setResponseFieldDescriptions(JSON.toJSONString(resourceDefinition.getResponseFieldDescriptions(), SerializerFeature.WriteClassName)); + } + + return resource; + } + + /** + * SysResource实体转化为ResourceDefinition对象 + * + * @author fengshuonan + * @date 2020/12/9 14:15 + */ + public static ResourceDefinition createResourceDefinition(SysResource sysResource) { + + ResourceDefinition resourceDefinition = new ResourceDefinition(); + + // 拷贝公共属性 + BeanUtil.copyProperties(sysResource, resourceDefinition, CopyOptions.create().ignoreError()); + + // 设置是否需要登录标识,Y为需要登录 + resourceDefinition.setRequiredLogin(YesOrNotEnum.Y.name().equals(sysResource.getRequiredLoginFlag())); + + // 设置是否需要权限认证标识,Y为需要权限认证 + resourceDefinition.setRequiredPermission(YesOrNotEnum.Y.name().equals(sysResource.getRequiredPermissionFlag())); + + // 转化校验组 + if (ObjectUtil.isNotEmpty(sysResource.getValidateGroups())) { + resourceDefinition.setValidateGroups(JSON.parseObject(sysResource.getValidateGroups(), Set.class, Feature.SupportAutoType)); + } + + // 转化接口参数的字段描述 + if (ObjectUtil.isNotEmpty(sysResource.getParamFieldDescriptions())) { + resourceDefinition.setParamFieldDescriptions(JSON.parseObject(sysResource.getParamFieldDescriptions(), Set.class, Feature.SupportAutoType)); + } + + // 转化接口返回结果的字段描述 + if (ObjectUtil.isNotEmpty(sysResource.getResponseFieldDescriptions())) { + resourceDefinition.setResponseFieldDescriptions(JSON.parseObject(sysResource.getResponseFieldDescriptions(), Set.class, Feature.SupportAutoType)); + } + + return resourceDefinition; + } + +} diff --git a/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/mapper/SysResourceMapper.java b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/mapper/SysResourceMapper.java new file mode 100644 index 000000000..cbf748709 --- /dev/null +++ b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/mapper/SysResourceMapper.java @@ -0,0 +1,14 @@ +package cn.stylefeng.roses.kernel.resource.modular.mapper; + +import cn.stylefeng.roses.kernel.resource.modular.entity.SysResource; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 资源表 Mapper 接口 + * + * @author fengshuonan + * @date 2020/11/23 22:45 + */ +public interface SysResourceMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/mapper/mapping/SysResourceMapper.xml b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/mapper/mapping/SysResourceMapper.xml new file mode 100644 index 000000000..8618cf6cd --- /dev/null +++ b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/mapper/mapping/SysResourceMapper.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/service/SysResourceService.java b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/service/SysResourceService.java new file mode 100644 index 000000000..d32961a17 --- /dev/null +++ b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/service/SysResourceService.java @@ -0,0 +1,49 @@ +package cn.stylefeng.roses.kernel.resource.modular.service; + +import cn.stylefeng.roses.kernel.resource.modular.entity.SysResource; +import cn.stylefeng.roses.kernel.system.pojo.resource.request.ResourceRequest; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * 资源服务类 + * + * @author fengshuonan + * @date 2020/11/24 19:56 + */ +public interface SysResourceService extends IService { + + /** + * 获取资源分页列表 + * + * @param resourceRequest 请求参数 + * @return 返回结果 + * @author fengshuonan + * @date 2020/11/24 20:45 + */ + Page getResourceList(ResourceRequest resourceRequest); + + /** + * 通过应用code获取获取资源下拉列表 + *

+ * 只获取菜单资源 + * + * @param resourceRequest 请求参数 + * @return 响应下拉结果 + * @author fengshuonan + * @date 2020/11/24 20:45 + */ + List getMenuResourceList(ResourceRequest resourceRequest); + + /** + * 删除某个项目的所有资源 + * + * @param projectCode 项目编码,一般为spring application name + * @author fengshuonan + * @date 2020/11/24 20:46 + */ + void deleteResourceByProjectCode(String projectCode); + +} \ No newline at end of file diff --git a/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/service/impl/SysResourceServiceImpl.java b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/service/impl/SysResourceServiceImpl.java new file mode 100644 index 000000000..50de7f83e --- /dev/null +++ b/kernel-s-system/system-business-resource/src/main/java/cn/stylefeng/roses/kernel/resource/modular/service/impl/SysResourceServiceImpl.java @@ -0,0 +1,221 @@ +package cn.stylefeng.roses.kernel.resource.modular.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.resource.api.ResourceReportApi; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ReportResourceParam; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceDefinition; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceUrlParam; +import cn.stylefeng.roses.kernel.resource.modular.cache.ResourceCache; +import cn.stylefeng.roses.kernel.resource.modular.entity.SysResource; +import cn.stylefeng.roses.kernel.resource.modular.factory.ResourceFactory; +import cn.stylefeng.roses.kernel.resource.modular.mapper.SysResourceMapper; +import cn.stylefeng.roses.kernel.resource.modular.service.SysResourceService; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import cn.stylefeng.roses.kernel.system.ResourceServiceApi; +import cn.stylefeng.roses.kernel.system.pojo.resource.request.ResourceRequest; +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 com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.RequestBody; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 资源表 服务实现类 + * + * @author fengshuonan + * @date 2020/11/23 22:45 + */ +@Service +public class SysResourceServiceImpl extends ServiceImpl implements SysResourceService, ResourceReportApi, ResourceServiceApi { + + @Autowired + private SysResourceMapper resourceMapper; + + @Autowired + private ResourceCache resourceCache; + + @Override + public Page getResourceList(ResourceRequest resourceRequest) { + Page page = PageFactory.defaultPage(); + LambdaQueryWrapper wrapper = createWrapper(resourceRequest); + return this.page(page, wrapper); + } + + @Override + public List getMenuResourceList(ResourceRequest resourceRequest) { + + LambdaQueryWrapper wrapper = createWrapper(resourceRequest); + + // 查询为菜单的 + wrapper.eq(SysResource::getMenuFlag, YesOrNotEnum.Y.getCode()); + + // 只查询code和name + wrapper.select(SysResource::getCode, SysResource::getName); + + List menuResourceList = this.list(wrapper); + + // 增加返回虚拟菜单的情况 + SysResource sysResource = new SysResource(); + sysResource.setCode(""); + sysResource.setName("虚拟目录(空)"); + menuResourceList.add(0, sysResource); + + return menuResourceList; + } + + @Override + public void deleteResourceByProjectCode(String projectCode) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysResource::getProjectCode, projectCode); + this.remove(wrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void reportResources(@RequestBody ReportResourceParam reportResourceReq) { + + String projectCode = reportResourceReq.getProjectCode(); + Map> resourceDefinitions = reportResourceReq.getResourceDefinitions(); + + if (ObjectUtil.isEmpty(projectCode) || resourceDefinitions == null) { + return; + } + + //根据project删除该项目下的所有资源 + this.deleteResourceByProjectCode(projectCode); + + //获取当前应用的所有资源 + ArrayList allResources = new ArrayList<>(); + for (Map.Entry> appModularResources : resourceDefinitions.entrySet()) { + Map value = appModularResources.getValue(); + for (Map.Entry modularResources : value.entrySet()) { + SysResource resource = ResourceFactory.createResource(modularResources.getValue()); + allResources.add(resource); + } + } + + //将资源存入库中 + this.saveBatch(allResources, allResources.size()); + + //将资源存入缓存一份 + resourceCache.saveResourcesToCache(allResources); + } + + @Override + public ResourceDefinition getResourceByUrl(@RequestBody ResourceUrlParam resourceUrlReq) { + if (ObjectUtil.isEmpty(resourceUrlReq.getUrl())) { + return null; + } else { + + List resources = resourceMapper.selectList(new QueryWrapper().eq("url", resourceUrlReq.getUrl())); + + if (resources == null || resources.isEmpty()) { + return null; + } else { + + // 获取接口资源信息 + SysResource resource = resources.get(0); + ResourceDefinition resourceDefinition = new ResourceDefinition(); + BeanUtils.copyProperties(resource, resourceDefinition); + + // 获取是否需要登录的标记, 判断是否需要登录,如果是则设置为true,否则为false + String requiredLoginFlag = resource.getRequiredLoginFlag(); + resourceDefinition.setRequiredLogin(YesOrNotEnum.Y.name().equals(requiredLoginFlag)); + + // 获取请求权限的标记,判断是否有权限,如果有则设置为true,否则为false + String requiredPermissionFlag = resource.getRequiredPermissionFlag(); + resourceDefinition.setRequiredPermission(YesOrNotEnum.Y.name().equals(requiredPermissionFlag)); + + // 获取是否是菜单的flag,如果是则设置为true,否则为false + String menuFlag = resource.getMenuFlag(); + resourceDefinition.setMenuFlag(YesOrNotEnum.Y.name().equals(menuFlag)); + + return resourceDefinition; + } + } + } + + @Override + public List getResourceListByIds(List resourceIds) { + + ArrayList resourceDefinitions = new ArrayList<>(); + + if (resourceIds == null || resourceIds.isEmpty()) { + return resourceDefinitions; + } + + // 拼接in条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysResource::getId, resourceIds); + + // 获取资源详情 + List list = this.list(queryWrapper); + for (SysResource sysResource : list) { + ResourceDefinition resourceDefinition = new ResourceDefinition(); + BeanUtil.copyProperties(sysResource, resourceDefinition); + resourceDefinitions.add(resourceDefinition); + } + + return resourceDefinitions; + } + + @Override + public List getResourceUrlsListByIds(List resourceIds) { + + ArrayList resourceUrls = new ArrayList<>(); + + if (resourceIds == null || resourceIds.isEmpty()) { + return resourceUrls; + } + + // 拼接in条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysResource::getId, resourceIds); + queryWrapper.select(SysResource::getUrl); + + // 获取资源详情 + List list = this.list(queryWrapper); + return list.stream().map(SysResource::getUrl).collect(Collectors.toList()); + } + + /** + * 创建wrapper + * + * @author fengshuonan + * @date 2020/11/6 10:16 + */ + private LambdaQueryWrapper createWrapper(ResourceRequest resourceRequest) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + if (ObjectUtil.isNotNull(resourceRequest)) { + // 根据应用编码查询 + if (ObjectUtil.isNotEmpty(resourceRequest.getAppCode())) { + queryWrapper.eq(SysResource::getAppCode, resourceRequest.getAppCode()); + } + + // 根据资源名称 + if (ObjectUtil.isNotEmpty(resourceRequest.getResourceName())) { + queryWrapper.like(SysResource::getName, resourceRequest.getResourceName()); + } + + // 根据是否是菜单查询 + if (ObjectUtil.isNotEmpty(resourceRequest.getMenuFlag())) { + queryWrapper.like(SysResource::getMenuFlag, resourceRequest.getMenuFlag()); + } + } + + return queryWrapper; + } + +} \ No newline at end of file diff --git a/kernel-s-system/system-business-role/README.md b/kernel-s-system/system-business-role/README.md new file mode 100644 index 000000000..8155b7427 --- /dev/null +++ b/kernel-s-system/system-business-role/README.md @@ -0,0 +1,3 @@ +包含角色管理业务 + +角色可以关联资源和资源组 \ No newline at end of file diff --git a/kernel-s-system/system-business-role/pom.xml b/kernel-s-system/system-business-role/pom.xml new file mode 100644 index 000000000..b11b08f38 --- /dev/null +++ b/kernel-s-system/system-business-role/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-system + 1.0.0 + ../pom.xml + + + system-business-role + + jar + + + + + + cn.stylefeng.roses + system-api + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/controller/SysRoleController.java b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/controller/SysRoleController.java new file mode 100644 index 000000000..11dbd708f --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/controller/SysRoleController.java @@ -0,0 +1,154 @@ +package cn.stylefeng.roses.kernel.role.modular.controller; + +import cn.hutool.core.collection.ListUtil; +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.role.modular.service.SysRoleResourceService; +import cn.stylefeng.roses.kernel.role.modular.service.SysRoleService; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import cn.stylefeng.roses.kernel.system.RoleServiceApi; +import cn.stylefeng.roses.kernel.system.pojo.role.request.SysRoleRequest; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * 系统角色控制器 + * + * @author majianguo + * @date 2020/11/5 上午10:19 + */ +@RestController +@ApiResource(name = "系统角色管理") +public class SysRoleController { + + @Resource + private RoleServiceApi roleServiceApi; + + @Resource + private SysRoleService sysRoleService; + + @Resource + private SysRoleResourceService sysRoleResourceService; + + /** + * 添加系统角色 + * + * @author majianguo + * @date 2020/11/5 上午10:38 + */ + @PostResource(name = "添加系统", path = "/sysRole/add") + public ResponseData add(@RequestBody @Validated(SysRoleRequest.add.class) SysRoleRequest sysRoleRequest) { + sysRoleService.add(sysRoleRequest); + return new SuccessResponseData(); + } + + /** + * 编辑系统角色 + * + * @author majianguo + * @date 2020/11/5 上午10:49 + */ + @PostResource(name = "角色编辑", path = "/sysRole/edit") + public ResponseData edit(@RequestBody @Validated(SysRoleRequest.edit.class) SysRoleRequest sysRoleRequest) { + sysRoleService.edit(sysRoleRequest); + return new SuccessResponseData(); + } + + /** + * 删除系统角色 + * + * @author majianguo + * @date 2020/11/5 上午10:48 + */ + @PostResource(name = "角色删除", path = "/sysRole/delete") + public ResponseData delete(@RequestBody @Validated(SysRoleRequest.delete.class) SysRoleRequest sysRoleRequest) { + sysRoleService.delete(sysRoleRequest); + return new SuccessResponseData(); + } + + /** + * 授权角色对应的资源 + * + * @author fengshuonan + * @date 2020/11/22 19:51 + */ + @PostResource(name = "授权资源", path = "/sysRole/grantResource") + public ResponseData grantResource(@RequestBody @Validated(SysRoleRequest.grantResource.class) SysRoleRequest sysRoleParam) { + sysRoleResourceService.grantResource(sysRoleParam); + return new SuccessResponseData(); + } + + /** + * 授权数据 + * + * @author fengshuonan + * @date 2020/3/28 16:05 + */ + @PostResource(name = "授权数据", path = "/sysRole/grantData") + public ResponseData grantData(@RequestBody @Validated(SysRoleRequest.grantData.class) SysRoleRequest sysRoleParam) { + sysRoleService.grantData(sysRoleParam); + return new SuccessResponseData(); + } + + /** + * 查看系统角色 + * + * @author majianguo + * @date 2020/11/5 上午10:50 + */ + @GetResource(name = "角色查看", path = "/sysRole/detail") + public ResponseData detail(@Validated(SysRoleRequest.detail.class) SysRoleRequest sysRoleRequest) { + return new SuccessResponseData(sysRoleService.detail(sysRoleRequest)); + } + + /** + * 查询系统角色 + * + * @author majianguo + * @date 2020/11/5 上午10:19 + */ + @GetResource(name = "查询角色", path = "/sysRole/page") + public ResponseData page(@RequestBody SysRoleRequest sysRoleRequest) { + return new SuccessResponseData(sysRoleService.page(sysRoleRequest)); + } + + /** + * 系统角色下拉(用于授权角色时选择) + * + * @author majianguo + * @date 2020/11/6 13:49 + */ + @GetResource(name = "角色下拉", path = "/sysRole/dropDown") + public ResponseData dropDown() { + return new SuccessResponseData(sysRoleService.dropDown()); + } + + /** + * 拥有菜单 + * + * @author majianguo + * @date 2020/11/5 上午10:58 + */ + @GetResource(name = "角色拥有菜单", path = "/sysRole/getRoleMenus") + public ResponseData getRoleMenus(@Validated(SysRoleRequest.detail.class) SysRoleRequest sysRoleRequest) { + Long roleId = sysRoleRequest.getId(); + return new SuccessResponseData(roleServiceApi.getMenuIdsByRoleIds(ListUtil.toList(roleId))); + } + + /** + * 拥有数据 + * + * @author majianguo + * @date 2020/11/5 上午10:59 + */ + @GetResource(name = "角色拥有数据", path = "/sysRole/getRoleDataScope") + public ResponseData getRoleDataScope(@Validated(SysRoleRequest.detail.class) SysRoleRequest sysRoleRequest) { + return new SuccessResponseData(sysRoleService.getRoleDataScope(sysRoleRequest)); + } + +} diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/entity/SysRole.java b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/entity/SysRole.java new file mode 100644 index 000000000..94d53c93b --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/entity/SysRole.java @@ -0,0 +1,94 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.role.modular.entity; + +import com.baomidou.mybatisplus.annotation.*; +import cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 系统角色表 + * + * @author majianguo + * @date 2020/11/5 下午4:32 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_role") +public class SysRole extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + @TableField("id") + private Long id; + + /** + * 名称 + */ + @TableField("name") + private String name; + + /** + * 编码 + */ + @TableField("code") + private String code; + + /** + * 排序 + */ + @TableField("sort") + private BigDecimal sort; + + /** + * 数据范围类型(枚举 10全部数据 20本部门及以下数据 30本部门数据 40仅本人数据 50自定义数据) + */ + @TableField("data_scope_type") + private Integer dataScopeType; + + /** + * 状态(1-启用,2-禁用) + */ + @TableField("status_flag") + private Integer statusFlag; + + /** + * 备注 + */ + @TableField(value = "remark", insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED) + private String remark; + + /** + * 删除标记(Y-已删除,N-未删除) + */ + @TableField("del_flag") + private String delFlag; + +} diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/entity/SysRoleDataScope.java b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/entity/SysRoleDataScope.java new file mode 100644 index 000000000..eaafb4f1c --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/entity/SysRoleDataScope.java @@ -0,0 +1,64 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.role.modular.entity; + +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 cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 系统角色数据范围表 + * + * @author majianguo + * @date 2020/11/5 下午4:29 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_role_data_scope") +public class SysRoleDataScope extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + @TableField("id") + private Long id; + + /** + * 角色id + */ + @TableField("roleId") + private Long roleId; + + /** + * 机构id + */ + @TableField("organizationId") + private Long organizationId; +} diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/entity/SysRoleResource.java b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/entity/SysRoleResource.java new file mode 100644 index 000000000..a23effb99 --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/entity/SysRoleResource.java @@ -0,0 +1,65 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.role.modular.entity; + +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 cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 角色资源关联 + * + * @author fengshuonan + * @date 2020/11/5 下午4:30 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_role_resource") +public class SysRoleResource extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + @TableField("id") + private Long id; + + /** + * 角色id + */ + @TableField("role_id") + private Long roleId; + + /** + * 资源id + */ + @TableField("resource_id") + private String resourceId; + +} diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/SysRoleDataScopeMapper.java b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/SysRoleDataScopeMapper.java new file mode 100644 index 000000000..dec6d51bf --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/SysRoleDataScopeMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.role.modular.mapper; + +import cn.stylefeng.roses.kernel.role.modular.entity.SysRoleDataScope; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 系统角色范围mapper接口 + * + * @author majianguo + * @date 2020/11/5 下午4:15 + */ +public interface SysRoleDataScopeMapper extends BaseMapper { +} diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/SysRoleMapper.java b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/SysRoleMapper.java new file mode 100644 index 000000000..80f74a225 --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/SysRoleMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.role.modular.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.stylefeng.roses.kernel.role.modular.entity.SysRole; + +/** + * 系统角色mapper接口 + * + * @author majianguo + * @date 2020/11/5 下午4:15 + */ +public interface SysRoleMapper extends BaseMapper { +} diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/SysRoleMenuMapper.java b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/SysRoleMenuMapper.java new file mode 100644 index 000000000..0395bda5f --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/SysRoleMenuMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.role.modular.mapper; + +import cn.stylefeng.roses.kernel.role.modular.entity.SysRoleResource; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 系统角色菜单mapper接口 + * + * @author majianguo + * @date 2020/11/5 下午4:15 + */ +public interface SysRoleMenuMapper extends BaseMapper { +} diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/mapping/SysRoleDataScopeMapper.xml b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/mapping/SysRoleDataScopeMapper.xml new file mode 100644 index 000000000..aaff8f4dd --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/mapping/SysRoleDataScopeMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/mapping/SysRoleMapper.xml b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/mapping/SysRoleMapper.xml new file mode 100644 index 000000000..31bf0afdd --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/mapping/SysRoleMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/mapping/SysRoleMenuMapper.xml b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/mapping/SysRoleMenuMapper.xml new file mode 100644 index 000000000..a474e9ddf --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/mapper/mapping/SysRoleMenuMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/SysRoleDataScopeService.java b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/SysRoleDataScopeService.java new file mode 100644 index 000000000..a56da385d --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/SysRoleDataScopeService.java @@ -0,0 +1,55 @@ +package cn.stylefeng.roses.kernel.role.modular.service; + +import cn.stylefeng.roses.kernel.role.modular.entity.SysRoleDataScope; +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.system.pojo.role.request.SysRoleRequest; + +import java.util.List; +import java.util.Set; + +/** + * 系统角色数据范围service接口 + * + * @author majianguo + * @date 2020/11/5 上午11:21 + */ +public interface SysRoleDataScopeService extends IService { + + /** + * 授权数据 + * + * @param sysRoleRequest 授权参数 + * @author majianguo + * @date 2020/11/5 上午11:20 + */ + void grantDataScope(SysRoleRequest sysRoleRequest); + + /** + * 根据角色id获取角色数据范围集合 + * + * @param roleIdList 角色id集合 + * @return 数据范围id集合 + * @author majianguo + * @date 2020/11/5 上午11:21 + */ + List getRoleDataScopeIdList(List roleIdList); + + /** + * 根据机构id集合删除对应的角色-数据范围关联信息 + * + * @param orgIdList 机构id集合 + * @author majianguo + * @date 2020/11/5 上午11:21 + */ + void deleteRoleDataScopeListByOrgIdList(Set orgIdList); + + /** + * 根据角色id删除对应的角色-数据范围关联信息 + * + * @param roleId 角色id + * @author majianguo + * @date 2020/11/5 上午11:21 + */ + void deleteRoleDataScopeListByRoleId(Long roleId); + +} diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/SysRoleResourceService.java b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/SysRoleResourceService.java new file mode 100644 index 000000000..ebb3ad266 --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/SysRoleResourceService.java @@ -0,0 +1,68 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.role.modular.service; + +import cn.stylefeng.roses.kernel.role.modular.entity.SysRoleResource; +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.system.pojo.role.request.SysRoleRequest; + +import java.util.List; + +/** + * 系统角色菜单service接口 + * + * @author majianguo + * @date 2020/11/5 上午11:17 + */ +public interface SysRoleResourceService extends IService { + + /** + * 授权资源 + * + * @param sysRoleRequest 授权参数 + * @author majianguo + * @date 2020/11/5 上午11:17 + */ + void grantResource(SysRoleRequest sysRoleRequest); + + /** + * 根据资源id集合删除角色关联的资源 + * + * @param resourceIds 资源id集合 + * @author majianguo + * @date 2020/11/5 上午11:17 + */ + void deleteRoleResourceListByResourceIds(List resourceIds); + + /** + * 根据角色id删除对应的角色资源信息 + * + * @param roleId 角色id + * @author majianguo + * @date 2020/11/5 上午11:18 + */ + void deleteRoleResourceListByRoleId(Long roleId); + +} diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/SysRoleService.java b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/SysRoleService.java new file mode 100644 index 000000000..d103a018b --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/SysRoleService.java @@ -0,0 +1,149 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.role.modular.service; + +import cn.stylefeng.roses.kernel.role.modular.entity.SysRole; +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.rule.pojo.dict.SimpleDict; +import cn.stylefeng.roses.kernel.system.pojo.role.request.SysRoleRequest; +import cn.stylefeng.roses.kernel.system.pojo.role.response.SysRoleResponse; + +import java.util.List; + +/** + * 系统角色service接口 + * + * @author majianguo + * @date 2020/11/5 上午11:12 + */ +public interface SysRoleService extends IService { + + /** + * 添加系统角色 + * + * @param sysRoleRequest 添加参数 + * @author majianguo + * @date 2020/11/5 上午11:13 + */ + void add(SysRoleRequest sysRoleRequest); + + /** + * 编辑系统角色 + * + * @param sysRoleRequest 编辑参数 + * @author majianguo + * @date 2020/11/5 上午11:14 + */ + void edit(SysRoleRequest sysRoleRequest); + + /** + * 删除系统角色 + * + * @param sysRoleRequest 删除参数 + * @author majianguo + * @date 2020/11/5 上午11:14 + */ + void delete(SysRoleRequest sysRoleRequest); + + /** + * 授权数据范围(组织机构) + * + * @param sysRoleRequest 授权参数 + * @author majianguo + * @date 2020/11/5 上午11:14 + */ + void grantData(SysRoleRequest sysRoleRequest); + + /** + * 查看系统角色 + * + * @param sysRoleRequest 查看参数 + * @return 系统角色 + * @author majianguo + * @date 2020/11/5 上午11:14 + */ + SysRoleResponse detail(SysRoleRequest sysRoleRequest); + + /** + * 查询系统角色 + * + * @param sysRoleRequest 查询参数 + * @return 查询分页结果 + * @author majianguo + * @date 2020/11/5 上午11:13 + */ + PageResult page(SysRoleRequest sysRoleRequest); + + /** + * 系统角色下拉(用于授权角色时选择) + * + * @return 增强版hashMap,格式:[{"id":456, "code":"zjl", "name":"总经理"}] + * @author majianguo + * @date 2020/11/5 上午11:13 + */ + List dropDown(); + + /*** + * 查询角色拥有数据 + * + * @param sysRoleRequest 查询参数 + * @return 数据范围id集合 + * @author majianguo + * @date 2020/11/5 上午11:15 + */ + List getRoleDataScope(SysRoleRequest sysRoleRequest); + + /** + * 获取用户角色相关信息 + * + * @param userId 用户id + * @return 增强版hashMap,格式:[{"id":456, "code":"zjl", "name":"总经理"}] + * @author majianguo + * @date 2020/11/5 上午11:12 + */ + List getLoginRoles(Long userId); + + /** + * 根据角色名模糊搜索系统角色列表 + * + * @param sysRoleRequest 查询参数 + * @return 增强版hashMap,格式:[{"id":456, "name":"总经理(zjl)"}] + * @author majianguo + * @date 2020/11/5 上午11:13 + */ + List list(SysRoleRequest sysRoleRequest); + + /** + * 根据角色id获取角色名称 + * + * @param roleId 角色id + * @return 角色名称 + * @author majianguo + * @date 2020/11/5 上午11:15 + */ + String getNameByRoleId(Long roleId); + +} diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/impl/SysRoleDataScopeServiceImpl.java b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/impl/SysRoleDataScopeServiceImpl.java new file mode 100644 index 000000000..595a0f55e --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/impl/SysRoleDataScopeServiceImpl.java @@ -0,0 +1,95 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.role.modular.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.role.modular.entity.SysRoleDataScope; +import cn.stylefeng.roses.kernel.role.modular.mapper.SysRoleDataScopeMapper; +import cn.stylefeng.roses.kernel.role.modular.service.SysRoleDataScopeService; +import cn.stylefeng.roses.kernel.system.pojo.role.request.SysRoleRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Set; + +/** + * 系统角色数据范围service接口实现类 + * + * @author majianguo + * @date 2020/11/5 下午4:32 + */ +@Service +public class SysRoleDataScopeServiceImpl extends ServiceImpl implements SysRoleDataScopeService { + + @Override + public void grantDataScope(SysRoleRequest sysRoleParam) { + Long roleId = sysRoleParam.getId(); + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysRoleDataScope::getRoleId, roleId); + + // 删除所拥该角色已绑定的范围 + this.remove(queryWrapper); + + // 授权该角色数据范围 + sysRoleParam.getGrantOrgIdList().forEach(orgId -> { + SysRoleDataScope sysRoleDataScope = new SysRoleDataScope(); + sysRoleDataScope.setRoleId(roleId); + sysRoleDataScope.setOrganizationId(orgId); + this.save(sysRoleDataScope); + }); + } + + @Override + public List getRoleDataScopeIdList(List roleIdList) { + List resultList = CollectionUtil.newArrayList(); + if (ObjectUtil.isNotEmpty(roleIdList)) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysRoleDataScope::getRoleId, roleIdList); + this.list(queryWrapper).forEach(sysRoleDataScope -> resultList.add(sysRoleDataScope.getOrganizationId())); + } + return resultList; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteRoleDataScopeListByOrgIdList(Set orgIdList) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysRoleDataScope::getOrganizationId, orgIdList); + this.remove(queryWrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteRoleDataScopeListByRoleId(Long roleId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysRoleDataScope::getRoleId, roleId); + this.remove(queryWrapper); + } +} diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/impl/SysRoleResourceServiceImpl.java b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/impl/SysRoleResourceServiceImpl.java new file mode 100644 index 000000000..69b2b079f --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/impl/SysRoleResourceServiceImpl.java @@ -0,0 +1,88 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.role.modular.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.role.modular.entity.SysRoleResource; +import cn.stylefeng.roses.kernel.role.modular.mapper.SysRoleMenuMapper; +import cn.stylefeng.roses.kernel.role.modular.service.SysRoleResourceService; +import cn.stylefeng.roses.kernel.system.pojo.role.request.SysRoleRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +/** + * 系统角色菜单service接口实现类 + * + * @author majianguo + * @date 2020/11/5 上午11:32 + */ +@Service +public class SysRoleResourceServiceImpl extends ServiceImpl implements SysRoleResourceService { + + @Override + @Transactional(rollbackFor = Exception.class) + public void grantResource(SysRoleRequest sysRoleRequest) { + + Long roleId = sysRoleRequest.getId(); + + // 删除所拥有角色关联的资源 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysRoleResource::getRoleId, roleId); + this.remove(queryWrapper); + + // 授权资源 + List grantResourceList = sysRoleRequest.getGrantResourceList(); + ArrayList sysRoleResources = new ArrayList<>(); + + // 批量保存角色授权资源 + for (String resourceId : grantResourceList) { + SysRoleResource sysRoleMenu = new SysRoleResource(); + sysRoleMenu.setRoleId(roleId); + sysRoleMenu.setResourceId(resourceId); + sysRoleResources.add(sysRoleMenu); + } + this.saveBatch(sysRoleResources); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteRoleResourceListByResourceIds(List resourceIds) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysRoleResource::getResourceId, resourceIds); + this.remove(queryWrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteRoleResourceListByRoleId(Long roleId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysRoleResource::getRoleId, roleId); + this.remove(queryWrapper); + } +} diff --git a/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/impl/SysRoleServiceImpl.java b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/impl/SysRoleServiceImpl.java new file mode 100644 index 000000000..4e7fbd679 --- /dev/null +++ b/kernel-s-system/system-business-role/src/main/java/cn/stylefeng/roses/kernel/role/modular/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,388 @@ +/* +Copyright [2020] [https://www.stylefeng.cn] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Guns源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns-separation +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns-separation +6.若您的项目无法满足以上几点,可申请商业授权,获取Guns商业授权许可,请在官网购买授权,地址为 https://www.stylefeng.cn + */ +package cn.stylefeng.roses.kernel.role.modular.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.auth.api.context.LoginContext; +import cn.stylefeng.roses.kernel.auth.api.enums.DataScopeTypeEnum; +import cn.stylefeng.roses.kernel.auth.api.exception.AuthException; +import cn.stylefeng.roses.kernel.auth.api.exception.enums.AuthExceptionEnum; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.role.modular.entity.SysRole; +import cn.stylefeng.roses.kernel.role.modular.entity.SysRoleDataScope; +import cn.stylefeng.roses.kernel.role.modular.entity.SysRoleResource; +import cn.stylefeng.roses.kernel.role.modular.mapper.SysRoleMapper; +import cn.stylefeng.roses.kernel.role.modular.service.SysRoleDataScopeService; +import cn.stylefeng.roses.kernel.role.modular.service.SysRoleResourceService; +import cn.stylefeng.roses.kernel.role.modular.service.SysRoleService; +import cn.stylefeng.roses.kernel.rule.enums.StatusEnum; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import cn.stylefeng.roses.kernel.rule.pojo.dict.SimpleDict; +import cn.stylefeng.roses.kernel.system.MenuServiceApi; +import cn.stylefeng.roses.kernel.system.RoleServiceApi; +import cn.stylefeng.roses.kernel.system.UserServiceApi; +import cn.stylefeng.roses.kernel.system.constants.SymbolConstant; +import cn.stylefeng.roses.kernel.system.exception.SystemModularException; +import cn.stylefeng.roses.kernel.system.exception.enums.SysRoleExceptionEnum; +import cn.stylefeng.roses.kernel.system.pojo.role.request.SysRoleRequest; +import cn.stylefeng.roses.kernel.system.pojo.role.response.SysRoleResponse; +import cn.stylefeng.roses.kernel.system.util.DataScopeUtil; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 系统角色service接口实现类 + * + * @author majianguo + * @date 2020/11/5 上午11:33 + */ +@Service +public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService, RoleServiceApi { + + @Resource + private UserServiceApi userServiceApi; + + @Resource + private SysRoleResourceService sysRoleResourceService; + + @Resource + private SysRoleDataScopeService sysRoleDataScopeService; + + @Resource + private MenuServiceApi menuServiceApi; + + @Override + public void add(SysRoleRequest sysRoleRequest) { + SysRole sysRole = new SysRole(); + BeanUtil.copyProperties(sysRoleRequest, sysRole); + + // 默认设置为启用 + sysRole.setStatusFlag(StatusEnum.ENABLE.getCode()); + this.save(sysRole); + } + + @Override + public void edit(SysRoleRequest sysRoleRequest) { + SysRole sysRole = this.querySysRole(sysRoleRequest); + BeanUtil.copyProperties(sysRoleRequest, sysRole); + + // 不能修改状态,用修改状态接口修改状态 + sysRole.setStatusFlag(null); + + this.updateById(sysRole); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(SysRoleRequest sysRoleRequest) { + SysRole sysRole = this.querySysRole(sysRoleRequest); + + // 逻辑删除,设为删除标志 + sysRole.setDelFlag(YesOrNotEnum.Y.getCode()); + + this.updateById(sysRole); + + Long id = sysRole.getId(); + + // 级联删除该角色对应的角色-数据范围关联信息 + sysRoleDataScopeService.deleteRoleDataScopeListByRoleId(id); + + // 级联删除该角色对应的用户-角色表关联信息 + userServiceApi.deleteUserRoleListByRoleId(id); + + // 级联删除该角色对应的角色-菜单表关联信息 + sysRoleResourceService.deleteRoleResourceListByRoleId(id); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void grantData(SysRoleRequest sysRoleRequest) { + SysRole sysRole = this.querySysRole(sysRoleRequest); + + // 获取当前用户是否是超级管理员 + boolean superAdmin = LoginContext.me().getSuperAdminFlag(); + + // 获取请求参数的数据范围类型 + Integer dataScopeType = sysRoleRequest.getDataScopeType(); + DataScopeTypeEnum dataScopeTypeEnum = DataScopeTypeEnum.codeToEnum(dataScopeType); + + // 如果登录用户不是超级管理员,则进行数据权限校验 + if (!superAdmin) { + + // 只有超级管理员可以授权全部范围 + if (DataScopeTypeEnum.ALL.equals(dataScopeTypeEnum)) { + throw new AuthException(AuthExceptionEnum.ONLY_SUPER_ERROR); + } + + // 数据范围类型为自定义,则判断当前用户有没有该公司的权限 + if (DataScopeTypeEnum.DEFINE.getCode().equals(dataScopeType)) { + for (Long orgId : sysRoleRequest.getGrantOrgIdList()) { + DataScopeUtil.validateDataScopeByOrganizationId(orgId); + } + } + } + + sysRole.setDataScopeType(sysRoleRequest.getDataScopeType()); + this.updateById(sysRole); + + // 绑定角色数据范围关联 + sysRoleDataScopeService.grantDataScope(sysRoleRequest); + } + + @Override + public SysRoleResponse detail(SysRoleRequest sysRoleRequest) { + SysRole sysRole = this.querySysRole(sysRoleRequest); + SysRoleResponse roleResponse = new SysRoleResponse(); + BeanUtil.copyProperties(sysRole, roleResponse); + + // 填充数据范围类型枚举 + roleResponse.setDataScopeTypeEnum(DataScopeTypeEnum.codeToEnum(sysRole.getDataScopeType())); + + return roleResponse; + } + + @Override + public PageResult page(SysRoleRequest sysRoleRequest) { + LambdaQueryWrapper wrapper = createWrapper(sysRoleRequest); + Page sysRolePage = this.page(PageFactory.defaultPage(), wrapper); + return PageResultFactory.createPageResult(sysRolePage); + } + + @Override + public List dropDown() { + List dictList = CollectionUtil.newArrayList(); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 如果当前登录用户不是超级管理员,则查询自己拥有的角色 + if (!LoginContext.me().getSuperAdminFlag()) { + + // 查询自己拥有的 + Set roles = LoginContext.me().getLoginUser().getRoles(); + + // 取出所有角色id + Set loginUserRoleIds = roles.stream().map(SimpleDict::getId).collect(Collectors.toSet()); + if (ObjectUtil.isEmpty(loginUserRoleIds)) { + return dictList; + } + queryWrapper.in(SysRole::getId, loginUserRoleIds); + } + + // 只查询正常状态 + queryWrapper.eq(SysRole::getStatusFlag, StatusEnum.ENABLE.getCode()).eq(SysRole::getDelFlag, YesOrNotEnum.N.getCode()); + + this.list(queryWrapper).forEach(sysRole -> { + SimpleDict simpleDict = new SimpleDict(); + simpleDict.setId(sysRole.getId()); + simpleDict.setCode(sysRole.getCode()); + simpleDict.setName(sysRole.getName()); + dictList.add(simpleDict); + }); + return dictList; + } + + @Override + public List getRoleDataScope(SysRoleRequest sysRoleRequest) { + SysRole sysRole = this.querySysRole(sysRoleRequest); + return sysRoleDataScopeService.getRoleDataScopeIdList(CollectionUtil.newArrayList(sysRole.getId())); + } + + @Override + public List getLoginRoles(Long userId) { + List dictList = CollectionUtil.newArrayList(); + + // 获取用户角色id集合 + List roleIdList = userServiceApi.getUserRoleIdList(userId); + if (ObjectUtil.isNotEmpty(roleIdList)) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysRole::getId, roleIdList) + .eq(SysRole::getStatusFlag, StatusEnum.ENABLE.getCode()) + .ne(SysRole::getDelFlag, YesOrNotEnum.N.getCode()); + + // 根据角色id集合查询并返回结果 + this.list(queryWrapper).forEach(sysRole -> { + SimpleDict simpleDict = new SimpleDict(); + simpleDict.setId(sysRole.getId()); + simpleDict.setCode(sysRole.getCode()); + simpleDict.setName(sysRole.getName()); + dictList.add(simpleDict); + }); + } + return dictList; + } + + @Override + public List list(SysRoleRequest sysRoleParam) { + List dictList = CollectionUtil.newArrayList(); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysRoleParam)) { + + // 根据角色名称或编码模糊查询 + if (ObjectUtil.isNotEmpty(sysRoleParam.getName())) { + queryWrapper.and(i -> i.like(SysRole::getName, sysRoleParam.getName()).or().like(SysRole::getCode, sysRoleParam.getName())); + } + } + + // 只查询正常状态 + queryWrapper.eq(SysRole::getStatusFlag, StatusEnum.ENABLE.getCode()); + + // 根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysRole::getSort); + this.list(queryWrapper).forEach(sysRole -> { + SimpleDict simpleDict = new SimpleDict(); + simpleDict.setId(sysRole.getId()); + simpleDict.setName(sysRole.getName() + SymbolConstant.LEFT_SQUARE_BRACKETS + sysRole.getCode() + SymbolConstant.RIGHT_SQUARE_BRACKETS); + dictList.add(simpleDict); + }); + return dictList; + } + + @Override + public String getNameByRoleId(Long roleId) { + SysRole sysRole = this.getById(roleId); + if (ObjectUtil.isEmpty(sysRole)) { + throw new SystemModularException(SysRoleExceptionEnum.ROLE_NOT_EXIST); + } + return sysRole.getName(); + } + + @Override + public void deleteRoleDataScopeListByOrgIdList(Set organizationIds) { + sysRoleDataScopeService.deleteRoleDataScopeListByOrgIdList(organizationIds); + } + + @Override + public List getRolesByIds(List roleIds) { + + ArrayList sysRoleResponses = new ArrayList<>(); + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysRole::getId, roleIds); + List sysRoles = this.list(queryWrapper); + + // 角色列表不为空,角色信息转化为SysRoleResponse + if (!sysRoles.isEmpty()) { + for (SysRole sysRole : sysRoles) { + SysRoleResponse sysRoleResponse = new SysRoleResponse(); + BeanUtil.copyProperties(sysRole, sysRoleResponse); + // 填充数据范围类型枚举 + sysRoleResponse.setDataScopeTypeEnum(DataScopeTypeEnum.codeToEnum(sysRole.getDataScopeType())); + sysRoleResponses.add(sysRoleResponse); + } + } + + return sysRoleResponses; + } + + @Override + public List getRoleDataScopes(List roleIds) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysRoleDataScope::getRoleId, roleIds); + + List list = this.sysRoleDataScopeService.list(queryWrapper); + if (!list.isEmpty()) { + return list.stream().map(SysRoleDataScope::getOrganizationId).collect(Collectors.toList()); + } else { + return new ArrayList<>(); + } + } + + @Override + public List getMenuIdsByRoleIds(List roleIds) { + + // 获取角色绑定的资源 + List roleResourceIdList = this.getRoleResourceList(roleIds); + + // 获取资源对应的菜单 + return this.menuServiceApi.getMenuIdsByResourceCodes(roleResourceIdList); + } + + @Override + public List getRoleResourceList(List roleIdList) { + List resourceList = CollectionUtil.newArrayList(); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysRoleResource::getRoleId, roleIdList); + sysRoleResourceService.list(queryWrapper).forEach(sysRoleResource -> resourceList.add(sysRoleResource.getResourceId())); + return resourceList; + } + + /** + * 获取系统角色 + * + * @param sysRoleRequest 请求信息 + * @author majianguo + * @date 2020/11/5 下午4:12 + */ + private SysRole querySysRole(SysRoleRequest sysRoleRequest) { + SysRole sysRole = this.getById(sysRoleRequest.getId()); + if (ObjectUtil.isNull(sysRole)) { + throw new SystemModularException(SysRoleExceptionEnum.ROLE_NOT_EXIST); + } + return sysRole; + } + + /** + * 创建查询wrapper + * + * @author fengshuonan + * @date 2020/11/22 15:14 + */ + private LambdaQueryWrapper createWrapper(SysRoleRequest sysRoleRequest) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysRoleRequest)) { + + // 根据名称模糊查询 + if (ObjectUtil.isNotEmpty(sysRoleRequest.getName())) { + queryWrapper.like(SysRole::getName, sysRoleRequest.getName()); + } + + // 根据编码模糊查询 + if (ObjectUtil.isNotEmpty(sysRoleRequest.getCode())) { + queryWrapper.like(SysRole::getCode, sysRoleRequest.getCode()); + } + } + + // 查询未删除的 + queryWrapper.eq(SysRole::getDelFlag, YesOrNotEnum.N.getCode()); + + // 根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysRole::getSort); + + return queryWrapper; + } + +} diff --git a/kernel-s-system/system-business-user/README.md b/kernel-s-system/system-business-user/README.md new file mode 100644 index 000000000..dc6cbbf10 --- /dev/null +++ b/kernel-s-system/system-business-user/README.md @@ -0,0 +1 @@ +包含用户信息的维护 \ No newline at end of file diff --git a/kernel-s-system/system-business-user/pom.xml b/kernel-s-system/system-business-user/pom.xml new file mode 100644 index 000000000..83b1627f3 --- /dev/null +++ b/kernel-s-system/system-business-user/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-system + 1.0.0 + ../pom.xml + + + system-business-user + + jar + + + + + + cn.stylefeng.roses + system-api + 1.0.0 + + + + + cn.stylefeng.roses + config-api + 1.0.0 + + + + + cn.stylefeng.roses + office-api + 1.0.0 + + + + + + cn.stylefeng.roses + file-api + 1.0.0 + + + + + + cn.stylefeng.roses + scanner-api + 1.0.0 + + + + + + cn.stylefeng.roses + validator-api + 1.0.0 + + + + + + cn.stylefeng.roses + db-sdk-mp + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/controller/SysUserController.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/controller/SysUserController.java new file mode 100644 index 000000000..1ea75101b --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/controller/SysUserController.java @@ -0,0 +1,225 @@ +package cn.stylefeng.roses.kernel.system.modular.user.controller; + +import cn.stylefeng.roses.kernel.resource.api.annotation.ApiResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.GetResource; +import cn.stylefeng.roses.kernel.resource.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData; +import cn.stylefeng.roses.kernel.system.modular.user.pojo.request.SysUserRequest; +import cn.stylefeng.roses.kernel.system.UserServiceApi; +import cn.stylefeng.roses.kernel.system.modular.user.service.SysUserService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 用户管理控制器 + * + * @author luojie + * @date 2020/11/6 09:47 + */ +@RestController +@ApiResource(name = "用户管理") +public class SysUserController { + + @Resource + private SysUserService sysUserService; + + @Resource + private UserServiceApi userServiceApi; + + /** + * 增加用户 + * + * @author luojie + * @date 2020/11/6 13:50 + */ + @PostResource(name = "系统用户_增加", path = "/sysUser/add") + public ResponseData add(@RequestBody @Validated(BaseRequest.add.class) SysUserRequest sysUserRequest) { + sysUserService.add(sysUserRequest); + return new SuccessResponseData(); + } + + /** + * 编辑系统用户 + * + * @author luojie + * @date 2020/11/6 13:50 + */ + @PostResource(name = "系统用户_编辑", path = "/sysUser/edit") + public ResponseData edit(@RequestBody @Validated(SysUserRequest.edit.class) SysUserRequest sysUserRequest) { + sysUserService.edit(sysUserRequest); + return new SuccessResponseData(); + } + + /** + * 更新信息 + * + * @author luojie + * @date 2020/11/6 13:50 + */ + @PostResource(name = "系统用户_更新信息", path = "/sysUser/updateInfo") + public ResponseData updateInfo(@RequestBody @Validated(SysUserRequest.updateInfo.class) SysUserRequest sysUserRequest) { + sysUserService.updateInfo(sysUserRequest); + return new SuccessResponseData(); + } + + /** + * 修改状态 + * + * @author luojie + * @date 2020/11/6 13:50 + */ + @PostResource(name = "系统用户_修改状态", path = "/sysUser/changeStatus") + public ResponseData changeStatus(@RequestBody @Validated(SysUserRequest.changeStatus.class) SysUserRequest sysUserRequest) { + sysUserService.changeStatus(sysUserRequest); + return new SuccessResponseData(); + } + + /** + * 修改密码 + * + * @author luojie + * @date 2020/11/6 13:50 + */ + @PostResource(name = "系统用户_修改密码", path = "/sysUser/updatePwd") + public ResponseData updatePwd(@RequestBody @Validated(SysUserRequest.updatePwd.class) SysUserRequest sysUserRequest) { + sysUserService.updatePwd(sysUserRequest); + return new SuccessResponseData(); + } + + /** + * 重置密码 + * + * @author luojie + * @date 2020/11/6 13:48 + */ + @PostResource(name = "系统用户_重置密码", path = "/sysUser/resetPwd") + public ResponseData resetPwd(@RequestBody @Validated(SysUserRequest.resetPwd.class) SysUserRequest sysUserRequest) { + sysUserService.resetPwd(sysUserRequest); + return new SuccessResponseData(); + } + + /** + * 修改头像 + * + * @author luojie + * @date 2020/11/6 13:48 + */ + @PostResource(name = "系统用户_修改头像", path = "/sysUser/updateAvatar") + public ResponseData updateAvatar(@RequestBody @Validated(SysUserRequest.updateAvatar.class) SysUserRequest sysUserRequest) { + sysUserService.updateAvatar(sysUserRequest); + return new SuccessResponseData(); + } + + /** + * 授权角色 + * + * @author luojie + * @date 2020/11/6 13:50 + */ + @PostResource(name = "系统用户_授权角色", path = "/sysUser/grantRole") + public ResponseData grantRole(@RequestBody @Validated(SysUserRequest.grantRole.class) SysUserRequest sysUserRequest) { + sysUserService.grantRole(sysUserRequest); + return new SuccessResponseData(); + } + + /** + * 授权数据 + * + * @author luojie + * @date 2020/11/6 13:50 + */ + @PostResource(name = "系统用户_授权数据", path = "/sysUser/grantData") + public ResponseData grantData(@RequestBody @Validated(SysUserRequest.grantData.class) SysUserRequest sysUserRequest) { + sysUserService.grantData(sysUserRequest); + return new SuccessResponseData(); + } + + /** + * 删除系统用户 + * + * @author luojie + * @date 2020/11/6 13:50 + */ + @PostResource(name = "系统用户_删除", path = "/sysUser/delete") + public ResponseData delete(@RequestBody @Validated(SysUserRequest.delete.class) SysUserRequest sysUserRequest) { + sysUserService.delete(sysUserRequest); + return new SuccessResponseData(); + } + + /** + * 查看系统用户 + * + * @author luojie + * @date 2020/11/6 13:50 + */ + @GetResource(name = "系统用户_查看", path = "/sysUser/detail") + public ResponseData detail(@Validated(SysUserRequest.detail.class) SysUserRequest sysUserRequest) { + return new SuccessResponseData(sysUserService.detail(sysUserRequest)); + } + + /** + * 查询系统用户 + * + * @author luojie + * @date 2020/11/6 13:49 + */ + @GetResource(name = "系统用户_查询", path = "/sysUser/page") + public ResponseData page(SysUserRequest sysUserRequest) { + return new SuccessResponseData(sysUserService.page(sysUserRequest)); + } + + /** + * 获取用户的角色列表 + * + * @author luojie + * @date 2020/11/6 13:50 + */ + @GetResource(name = "系统用户_获取用户的角色列表", path = "/sysUser/getUserRoles") + public ResponseData ownRole(@Validated(SysUserRequest.detail.class) SysUserRequest sysUserRequest) { + return new SuccessResponseData(sysUserService.getUserRoles(sysUserRequest)); + } + + /** + * 获取用户数据范围列表 + * + * @author luojie + * @date 2020/11/6 13:51 + */ + @GetResource(name = "系统用户_获取用户数据范围列表", path = "/sysUser/getUserDataScope") + public ResponseData ownData(@Validated(SysUserRequest.detail.class) SysUserRequest sysUserRequest) { + List userBindDataScope = userServiceApi.getUserBindDataScope(sysUserRequest.getId()); + return new SuccessResponseData(userBindDataScope); + } + + /** + * 用户下拉列表,可以根据姓名搜索 + * + * @param sysUserRequest 请求参数:name 姓名(可选) + * @return 返回除超级管理员外的用户列表 + * @author luojie + * @date 2020/11/6 09:49 + */ + @GetResource(name = "系统用户_选择器", path = "/sysUser/selector") + public ResponseData selector(SysUserRequest sysUserRequest) { + return new SuccessResponseData(sysUserService.selector(sysUserRequest)); + } + + /** + * 导出用户 + * + * @author luojie + * @date 2020/11/6 13:57 + */ + @GetResource(name = "系统用户_导出", path = "/sysUser/export") + public void export(HttpServletResponse response) { + sysUserService.export(response); + } + +} diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUser.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUser.java new file mode 100644 index 000000000..cc2d97770 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUser.java @@ -0,0 +1,136 @@ +package cn.stylefeng.roses.kernel.system.modular.user.entity; + +import com.alibaba.excel.annotation.ExcelProperty; +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 cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + * 系统用户表 + * + * @author luojie + * @date 2020/11/6 09:36 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_user") +public class SysUser extends BaseEntity { + + /** + * 主键 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 账号 + */ + @ExcelProperty(value = {"账号"}, index = 0) + @TableField("account") + private String account; + + /** + * 密码 + */ + @ExcelProperty(value = {"密码"}, index = 1) + @TableField("password") + private String password; + + /** + * 昵称 + */ + @ExcelProperty(value = {"昵称"}, index = 2) + @TableField("nick_name") + private String nickName; + + /** + * 姓名 + */ + @ExcelProperty(value = {"姓名"}, index = 3) + @TableField("name") + private String name; + + /** + * 头像 + */ + @ExcelProperty(value = {"头像"}, index = 4) + @TableField("avatar") + private Long avatar; + + /** + * 生日 + */ + @ExcelProperty(value = {"生日"}, index = 5) + @TableField("birthday") + private Date birthday; + + /** + * 性别(字典 M男 F女 ) + */ + @ExcelProperty(value = {"性别"}, index = 6) + @TableField("sex") + private String sex; + + /** + * 邮箱 + */ + @ExcelProperty(value = {"邮箱"}, index = 7) + @TableField("email") + private String email; + + /** + * 手机 + */ + @ExcelProperty(value = {"手机"}, index = 8) + @TableField("phone") + private String phone; + + /** + * 电话 + */ + @ExcelProperty(value = {"电话"}, index = 9) + @TableField("tel") + private String tel; + + /** + * 最后登陆IP + */ + @ExcelProperty(value = {"最后登陆IP"}, index = 10) + @TableField("last_login_ip") + private String lastLoginIp; + + /** + * 最后登陆时间 + */ + @ExcelProperty(value = {"最后登陆时间"}, index = 11) + @TableField("last_login_time") + private Date lastLoginTime; + + /** + * 是否是超级管理员,超级管理员可以拥有所有权限(Y-是,N-否) + */ + @ExcelProperty(value = {"是否是超级管理员"}, index = 12) + @TableField("super_admin_flag") + private String superAdminFlag; + + /** + * 状态(字典 1正常 2禁用 3冻结) + */ + @ExcelProperty(value = {"状态"}, index = 13) + @TableField("status_flag") + private Integer statusFlag; + + /** + * 删除标记(Y-已删除,N-未删除) + */ + @ExcelProperty(value = {"删除标记"}, index = 14) + @TableField("del_flag") + private String delFlag; + +} diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUserDataScope.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUserDataScope.java new file mode 100644 index 000000000..8a4c75727 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUserDataScope.java @@ -0,0 +1,36 @@ +package cn.stylefeng.roses.kernel.system.modular.user.entity; + +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 lombok.Data; + +/** + * 系统用户数据范围表 + * + * @author luojie + * @date 2020/11/6 09:46 + */ +@Data +@TableName("sys_user_data_scope") +public class SysUserDataScope { + + /** + * 主键 + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 用户id + */ + @TableField("user_id") + private Long userId; + + /** + * 机构id + */ + @TableField("organization_id") + private Long organizationId; +} diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUserRole.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUserRole.java new file mode 100644 index 000000000..2ac9b569f --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/entity/SysUserRole.java @@ -0,0 +1,36 @@ +package cn.stylefeng.roses.kernel.system.modular.user.entity; + +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 lombok.Data; + +/** + * 系统用户角色表 + * + * @author luojie + * @date 2020/11/6 09:46 + */ +@Data +@TableName("sys_user_role") +public class SysUserRole { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 用户id + */ + @TableField("user_id") + private Long userId; + + /** + * 角色id + */ + @TableField("role_id") + private Long roleId; +} diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/factory/LoginUserFactory.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/factory/LoginUserFactory.java new file mode 100644 index 000000000..f99707d28 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/factory/LoginUserFactory.java @@ -0,0 +1,129 @@ +package cn.stylefeng.roses.kernel.system.modular.user.factory; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; +import cn.stylefeng.roses.kernel.file.FileInfoApi; +import cn.stylefeng.roses.kernel.file.FileOperatorApi; +import cn.stylefeng.roses.kernel.file.expander.FileConfigExpander; +import cn.stylefeng.roses.kernel.file.pojo.response.SysFileInfoResponse; +import cn.stylefeng.roses.kernel.resource.api.pojo.resource.ResourceDefinition; +import cn.stylefeng.roses.kernel.rule.enums.SexEnum; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import cn.stylefeng.roses.kernel.rule.pojo.dict.SimpleDict; +import cn.stylefeng.roses.kernel.system.*; +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUser; +import cn.stylefeng.roses.kernel.system.pojo.organization.DataScopeResponse; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysEmployeeResponse; +import cn.stylefeng.roses.kernel.system.pojo.role.response.SysRoleResponse; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 组装当前登录用户的信息 + * + * @author fengshuonan + * @date 2020/11/26 22:25 + */ +public class LoginUserFactory { + + /** + * 创建登录用户 + * + * @author fengshuonan + * @date 2020/11/26 22:32 + */ + public static LoginUser createLoginUser(SysUser sysUser) { + + // 一些需要调用的别的模块的接口 + FileOperatorApi fileOperatorApi = SpringUtil.getBean(FileOperatorApi.class); + FileInfoApi fileInfoApi = SpringUtil.getBean(FileInfoApi.class); + RoleServiceApi roleServiceApi = SpringUtil.getBean(RoleServiceApi.class); + ResourceServiceApi resourceServiceApi = SpringUtil.getBean(ResourceServiceApi.class); + SysEmployeeApi sysEmployeeApi = SpringUtil.getBean(SysEmployeeApi.class); + DataScopeApi dataScopeApi = SpringUtil.getBean(DataScopeApi.class); + AppServiceApi appServiceApi = SpringUtil.getBean(AppServiceApi.class); + + // 填充基本信息 + LoginUser loginUser = new LoginUser(); + BeanUtil.copyProperties(sysUser, loginUser); + Long userId = sysUser.getId(); + + // 填充用户主组织机构id + SysEmployeeResponse userMainEmployee = sysEmployeeApi.getUserMainEmployee(userId); + loginUser.setOrganizationId(userMainEmployee.getOrganizationId()); + + // 获取头像文件详细信息 + SysFileInfoResponse fileInfoWithoutContent = fileInfoApi.getFileInfoWithoutContent(sysUser.getAvatar()); + // 填充用户头像url + String fileAuthUrl = fileOperatorApi.getFileAuthUrl( + fileInfoWithoutContent.getFileBucket(), + fileInfoWithoutContent.getFileObjectName(), + FileConfigExpander.getDefaultFileTimeoutSeconds() * 1000); + loginUser.setAvatar(fileAuthUrl); + + // 填充管理员转化 + loginUser.setSuperAdmin(YesOrNotEnum.Y.getCode().equals(sysUser.getSuperAdminFlag())); + + // 填充性别枚举转换 + loginUser.setSex(SexEnum.codeToEnum(sysUser.getSex()).getMessage()); + + // 填充手机号 + loginUser.setMobilePhone(sysUser.getPhone()); + + // 填充用户的数据范围 + DataScopeResponse dataScopeResponse = dataScopeApi.getDataScope(userId); + loginUser.setDataScopeTypes(dataScopeResponse.getDataScopeTypeEnums()); + loginUser.setOrganizationIdDataScope(dataScopeResponse.getOrganizationIds()); + loginUser.setUserIdDataScope(dataScopeResponse.getUserIds()); + + // 填充用户角色信息 + Set roleList = getRoleList(dataScopeResponse.getSysRoleResponses()); + loginUser.setRoles(roleList); + + // 通过用户角色,获取角色所有的资源id + List roleIds = roleList.stream().map(SimpleDict::getId).collect(Collectors.toList()); + List resourceIdsList = roleServiceApi.getRoleResourceList(roleIds); + // 根据资源id获取资源的所有详情信息 + List resourceDefinitions = resourceServiceApi.getResourceListByIds(resourceIdsList); + Set resourceUrls = resourceDefinitions.stream().map(ResourceDefinition::getUrl).collect(Collectors.toSet()); + // 填充用户的urls + loginUser.setResourceUrls(resourceUrls); + + // 用户包含的app信息 + Set appCodes = resourceDefinitions.stream().map(ResourceDefinition::getAppCode).collect(Collectors.toSet()); + Set appsByAppCodes = appServiceApi.getAppsByAppCodes(appCodes); + // 填充应用信息 + loginUser.setApps(appsByAppCodes); + + return loginUser; + } + + /** + * 组装角色信息 + * + * @author fengshuonan + * @date 2020/11/29 19:12 + */ + private static Set getRoleList(List sysRoleResponses) { + + if (sysRoleResponses == null || sysRoleResponses.isEmpty()) { + return new HashSet<>(); + } + + Set simpleRoles = new HashSet<>(); + for (SysRoleResponse sysRoleResponse : sysRoleResponses) { + SimpleDict simpleRole = new SimpleDict(); + simpleRole.setId(sysRoleResponse.getId()); + simpleRole.setName(sysRoleResponse.getName()); + simpleRole.setCode(sysRoleResponse.getCode()); + simpleRoles.add(simpleRole); + } + + return simpleRoles; + } + +} diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/factory/SysUserFactory.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/factory/SysUserFactory.java new file mode 100644 index 000000000..dd928725d --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/factory/SysUserFactory.java @@ -0,0 +1,96 @@ +package cn.stylefeng.roses.kernel.system.modular.user.factory; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.digest.BCrypt; +import cn.stylefeng.roses.kernel.auth.api.expander.AuthConfigExpander; +import cn.stylefeng.roses.kernel.rule.enums.SexEnum; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import cn.stylefeng.roses.kernel.system.enums.UserStatusEnum; +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUser; +import cn.stylefeng.roses.kernel.system.modular.user.pojo.request.SysUserRequest; + +/** + * 用户信息填充,用于创建和修改用户时,添加一些基础信息 + * + * @author fengshuonan + * @date 2020/11/21 12:55 + */ +public class SysUserFactory { + + /** + * 新增用户时候的用户信息填充 + * + * @author fengshuonan + * @date 2020/11/21 12:56 + */ + public static void fillAddSysUser(SysUser sysUser) { + + // 默认设置为非超级管理员 + sysUser.setSuperAdminFlag(YesOrNotEnum.N.getCode()); + + // 添加用户时,设置为启用状态 + sysUser.setStatusFlag(UserStatusEnum.ENABLE.getCode()); + + // 密码为空则设置为默认密码 + if (ObjectUtil.isEmpty(sysUser.getPassword())) { + String defaultPassword = AuthConfigExpander.getDefaultPassWord(); + sysUser.setPassword(BCrypt.hashpw(defaultPassword, BCrypt.gensalt())); + } else { + // 密码不为空,则将密码加密存储到库中 + sysUser.setPassword(BCrypt.hashpw(sysUser.getPassword(), BCrypt.gensalt())); + } + + // 用户头像为空 + if (ObjectUtil.isEmpty(sysUser.getAvatar())) { + sysUser.setAvatar(null); + } + + // 用户性别为空,则默认设置成男 + if (ObjectUtil.isEmpty(sysUser.getSex())) { + sysUser.setSex(SexEnum.M.getCode()); + } + } + + /** + * 编辑用户时候的用户信息填充 + * + * @author fengshuonan + * @date 2020/11/21 12:56 + */ + public static void fillEditSysUser(SysUser sysUser) { + + // 编辑用户不修改用户状态 + sysUser.setStatusFlag(null); + + // 不能修改原密码,通过重置密码或修改密码来修改 + sysUser.setPassword(null); + + } + + /** + * 编辑用户时候的用户信息填充 + * + * @author fengshuonan + * @date 2020/11/21 12:56 + */ + public static void fillUpdateInfo(SysUserRequest sysUserRequest, SysUser sysUser) { + + // 填充头像 + sysUser.setAvatar(sysUserRequest.getAvatar()); + + // 生日 + sysUser.setBirthday(DateUtil.parse(sysUserRequest.getBirthday())); + + // 性别(M-男,F-女) + sysUser.setSex(sysUserRequest.getSex()); + + // 邮箱 + sysUser.setEmail(sysUserRequest.getEmail()); + + // 手机 + sysUser.setPhone(sysUserRequest.getPhone()); + + } + +} diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/SysUserDataScopeMapper.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/SysUserDataScopeMapper.java new file mode 100644 index 000000000..5d06573b6 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/SysUserDataScopeMapper.java @@ -0,0 +1,14 @@ +package cn.stylefeng.roses.kernel.system.modular.user.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUserDataScope; + +/** + * 系统用户数据范围mapper接口 + * + * @author luojie + * @date 2020/11/6 14:50 + */ +public interface SysUserDataScopeMapper extends BaseMapper { + +} diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/SysUserMapper.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/SysUserMapper.java new file mode 100644 index 000000000..b963fb854 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/SysUserMapper.java @@ -0,0 +1,28 @@ +package cn.stylefeng.roses.kernel.system.modular.user.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUser; +import cn.stylefeng.roses.kernel.system.modular.user.pojo.request.SysUserRequest; +import cn.stylefeng.roses.kernel.system.modular.user.pojo.response.SysUserResponse; +import org.apache.ibatis.annotations.Param; + +/** + * 系统用户mapper接口 + * + * @author luojie + * @date 2020/11/6 14:50 + */ +public interface SysUserMapper extends BaseMapper { + + /** + * 查询用户列表 + * + * @param page 分页参数 + * @param sysUserRequest 查询条件信息 + * @author fengshuonan + * @date 2020/11/21 15:16 + */ + Page findUserPage(@Param("page") Page page, @Param("sysUserRequest") SysUserRequest sysUserRequest); + +} diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/SysUserRoleMapper.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/SysUserRoleMapper.java new file mode 100644 index 000000000..1904640ed --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/SysUserRoleMapper.java @@ -0,0 +1,15 @@ +package cn.stylefeng.roses.kernel.system.modular.user.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUserRole; + +/** + * 系统用户角色mapper接口 + * + * @author luojie + * @date 2020/11/6 14:50 + */ +public interface SysUserRoleMapper extends BaseMapper { + +} + diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/mapping/SysUserDataScopeMapper.xml b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/mapping/SysUserDataScopeMapper.xml new file mode 100644 index 000000000..fb48e3033 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/mapping/SysUserDataScopeMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/mapping/SysUserMapper.xml b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/mapping/SysUserMapper.xml new file mode 100644 index 000000000..bab96d6a1 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/mapping/SysUserMapper.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/mapping/SysUserRoleMapper.xml b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/mapping/SysUserRoleMapper.xml new file mode 100644 index 000000000..1256c0f5b --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/mapper/mapping/SysUserRoleMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/pojo/request/SysUserRequest.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/pojo/request/SysUserRequest.java new file mode 100644 index 000000000..5228eda19 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/pojo/request/SysUserRequest.java @@ -0,0 +1,246 @@ +package cn.stylefeng.roses.kernel.system.modular.user.pojo.request; + +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest; +import cn.stylefeng.roses.kernel.system.exception.SystemModularException; +import cn.stylefeng.roses.kernel.system.exception.enums.EmployeeExceptionEnum; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysEmployeeRequest; +import cn.stylefeng.roses.kernel.validator.validators.date.DateValue; +import cn.stylefeng.roses.kernel.validator.validators.status.StatusValue; +import cn.stylefeng.roses.kernel.validator.validators.unique.TableUniqueValue; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.Valid; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.List; + +/** + * 系统用户参数 + * + * @author luojie + * @date 2020/11/6 15:00 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysUserRequest extends BaseRequest { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class, start.class, stop.class, grantRole.class, grantData.class, updateInfo.class, updatePwd.class, resetPwd.class, changeStatus.class, updateAvatar.class}) + private Long id; + + /** + * 账号 + */ + @NotBlank(message = "账号不能为空,请检查account参数", groups = {add.class, edit.class}) + @TableUniqueValue( + message = "账号存在重复,请检查account参数", + groups = {add.class, edit.class}, + tableName = "sys_user", + columnName = "account") + private String account; + + /** + * 原密码 + */ + @NotBlank(message = "原密码不能为空,请检查password参数", groups = {updatePwd.class}) + private String password; + + /** + * 新密码 + */ + @NotBlank(message = "新密码不能为空,请检查newPassword参数", groups = {updatePwd.class}) + private String newPassword; + + /** + * 昵称 + */ + private String nickName; + + /** + * 姓名 + */ + @NotBlank(message = "姓名不能为空,请检查name参数", groups = {add.class, edit.class}) + private String name; + + /** + * 头像 + */ + @NotNull(message = "头像不能为空,请检查avatar参数", groups = {updateAvatar.class}) + private Long avatar; + + /** + * 生日 + */ + @DateValue(message = "生日格式不正确,请检查birthday参数", groups = {add.class, edit.class, updateInfo.class}) + private String birthday; + + /** + * 性别(M-男,F-女) + */ + @NotNull(message = "性别不能为空,请检查sex参数", groups = {updateInfo.class}) + private String sex; + + /** + * 邮箱 + */ + @Email(message = "邮箱格式错误,请检查email参数", groups = {updateInfo.class}) + private String email; + + /** + * 手机 + */ + @NotNull(message = "手机号码不能为空,请检查phone参数", groups = {add.class, edit.class, updateInfo.class}) + @Size(min = 11, max = 11, message = "手机号码格式错误,请检查手机是否是11位", groups = {add.class, edit.class, updateInfo.class}) + private String phone; + + /** + * 电话 + */ + private String tel; + + /** + * 授权角色,角色id集合 + */ + @NotNull(message = "授权角色不能为空,请检查grantRoleIdList参数", groups = {grantRole.class}) + private List grantRoleIdList; + + /** + * 授权数据范围,组织机构id集合 + */ + @NotNull(message = "授权数据不能为空,请检查grantOrgIdList参数", groups = {grantData.class}) + private List grantOrgIdList; + + /*==============员工相关信息==========*/ + + @NotNull(message = "员工信息不能为空,请检查sysEmpParam参数", groups = {add.class, edit.class}) + @Valid + private List sysEmployeeRequest; + + /** + * 状态(字典 1正常 2冻结) + */ + @NotNull(message = "状态不能为空,请检查statusFlag参数", groups = updateStatus.class) + @StatusValue(message = "状态不正确,请检查状态值是否正确", groups = updateStatus.class) + private Integer statusFlag; + + /** + * 机构id,这个参数用来查询用户时候传 + */ + private Long organizationId; + + /** + * 校验用户主部门是否设置,并且是否设置了多个 + *

+ * 如果不满足,则会抛出异常 + * + * @author fengshuonan + * @date 2020/11/21 12:52 + */ + public void validateUserMainEmployee() { + int i = 0; + + for (SysEmployeeRequest employeeRequest : sysEmployeeRequest) { + if (YesOrNotEnum.Y.getCode().equals(employeeRequest.getMainDeptFlag())) { + i++; + } + } + + // 如果有多个主部门,报错 + if (i == 0 || i > 1) { + throw new SystemModularException(EmployeeExceptionEnum.EMPLOYEE_NOT_OR_MANY); + } + } + + /** + * 获取用户的主部门信息 + * + * @author fengshuonan + * @date 2020/11/21 12:43 + */ + public SysEmployeeRequest getUserMainEmployee() { + + // 校验数据正确性 + this.validateUserMainEmployee(); + + // 查找用户主部门 + for (SysEmployeeRequest employeeRequest : sysEmployeeRequest) { + if (YesOrNotEnum.Y.getCode().equals(employeeRequest.getMainDeptFlag())) { + return employeeRequest; + } + } + + return null; + } + + /** + * 参数校验分组:修改密码 + */ + public @interface updatePwd { + + } + + /** + * 参数校验分组:重置密码 + */ + public @interface resetPwd { + + } + + /** + * 参数校验分组:修改头像 + */ + public @interface updateAvatar { + + } + + /** + * 参数校验分组:停用 + */ + public @interface stop { + + } + + /** + * 参数校验分组:启用 + */ + public @interface start { + + } + + /** + * 参数校验分组:更新信息 + */ + public @interface updateInfo { + + } + + /** + * 参数校验分组:授权角色 + */ + public @interface grantRole { + + } + + /** + * 参数校验分组:授权数据 + */ + public @interface grantData { + + } + + /** + * 参数校验分组:修改状态 + */ + public @interface changeStatus { + + } + +} + + diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/pojo/response/SysUserResponse.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/pojo/response/SysUserResponse.java new file mode 100644 index 000000000..f62e74d11 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/pojo/response/SysUserResponse.java @@ -0,0 +1,79 @@ +package cn.stylefeng.roses.kernel.system.modular.user.pojo.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysEmployeeResponse; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +/** + * 系统用户结果 + * + * @author fengshuonan + * @date 2020/4/2 9:19 + */ +@Data +public class SysUserResponse { + + /** + * 主键 + */ + private Long id; + + /** + * 账号 + */ + private String account; + + /** + * 昵称 + */ + private String nickName; + + /** + * 姓名 + */ + private String name; + + /** + * 头像 + */ + private Long avatar; + + /** + * 生日 + */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date birthday; + + /** + * 性别(字典 1男 2女 3未知) + */ + private String sex; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机 + */ + private String phone; + + /** + * 电话 + */ + private String tel; + + /** + * 用户员工信息 + */ + private List sysEmployeeResponse; + + /** + * 状态 + */ + private Integer statusFlag; +} diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/SysUserDataScopeService.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/SysUserDataScopeService.java new file mode 100644 index 000000000..9080566f2 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/SysUserDataScopeService.java @@ -0,0 +1,44 @@ +package cn.stylefeng.roses.kernel.system.modular.user.service; + +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUserDataScope; +import cn.stylefeng.roses.kernel.system.modular.user.pojo.request.SysUserRequest; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * 系统用户数据范围service接口 + * + * @author luojie + * @date 2020/11/6 10:28 + */ +public interface SysUserDataScopeService extends IService { + + /** + * 授权数据范围(组织机构id集合)给某个用户 + * + * @param sysUserRequest 授权参数 + * @author fengshuonan + * @date 2020/11/21 14:49 + */ + void grantData(SysUserRequest sysUserRequest); + + /** + * 获取用户的数据范围id集合 + * + * @param uerId 用户id + * @return 数据范围id集合 + * @author luojie + * @date 2020/11/6 15:01 + */ + List getUserDataScopeIdList(Long uerId); + + /** + * 根据用户id删除对应的用户-数据范围关联信息 + * + * @param userId 用户id + * @author luojie + * @date 2020/11/6 15:01 + */ + void deleteUserDataScopeListByUserId(Long userId); +} diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/SysUserRoleService.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/SysUserRoleService.java new file mode 100644 index 000000000..16c0a20c0 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/SysUserRoleService.java @@ -0,0 +1,32 @@ +package cn.stylefeng.roses.kernel.system.modular.user.service; + +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUserRole; +import cn.stylefeng.roses.kernel.system.modular.user.pojo.request.SysUserRequest; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + * 系统用户角色service接口 + * + * @author luojie + * @date 2020/11/6 10:28 + */ +public interface SysUserRoleService extends IService { + + /** + * 给某个用户授权角色 + * + * @param sysUserRequest 用户和角色id集合 + * @author fengshuonan + * @date 2020/11/21 14:44 + */ + void grantRole(SysUserRequest sysUserRequest); + + /** + * 根据用户id删除对应的用户-角色表关联信息 + * + * @param userId 用户id + * @author luojie + * @date 2020/11/6 15:03 + */ + void deleteUserRoleListByUserId(Long userId); +} diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/SysUserService.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/SysUserService.java new file mode 100644 index 000000000..b09199730 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/SysUserService.java @@ -0,0 +1,173 @@ +package cn.stylefeng.roses.kernel.system.modular.user.service; + +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUser; +import cn.stylefeng.roses.kernel.system.modular.user.pojo.request.SysUserRequest; +import cn.stylefeng.roses.kernel.system.modular.user.pojo.response.SysUserResponse; +import com.baomidou.mybatisplus.extension.service.IService; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.rule.pojo.dict.SimpleDict; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 系统用户service + * + * @author luojie + * @date 2020/11/6 10:28 + */ +public interface SysUserService extends IService { + + /** + * 增加用户 + * + * @param sysUserRequest 请求参数封装 + * @author fengshuonan + * @date 2020/11/21 12:32 + */ + void add(SysUserRequest sysUserRequest); + + /** + * 编辑用户 + * + * @param sysUserRequest 请求参数封装 + * @author fengshuonan + * @date 2020/11/21 12:32 + */ + void edit(SysUserRequest sysUserRequest); + + /** + * 更新用户信息(一般用于更新个人信息) + * + * @param sysUserRequest 请求参数封装 + * @author fengshuonan + * @date 2020/11/21 12:32 + */ + void updateInfo(SysUserRequest sysUserRequest); + + /** + * 修改状态 + * + * @param sysUserRequest 请求参数封装 + * @author fengshuonan + * @date 2020/11/21 14:19 + */ + void changeStatus(SysUserRequest sysUserRequest); + + /** + * 修改密码 + * + * @param sysUserRequest 请求参数封装 + * @author fengshuonan + * @date 2020/11/21 14:26 + */ + void updatePwd(SysUserRequest sysUserRequest); + + /** + * 重置密码 + * + * @param sysUserRequest 重置参数 + * @author luojie + * @date 2020/11/6 13:47 + */ + void resetPwd(SysUserRequest sysUserRequest); + + /** + * 修改头像 + * + * @param sysUserRequest 修改头像参数 + * @author luojie + * @date 2020/11/6 13:47 + */ + void updateAvatar(SysUserRequest sysUserRequest); + + /** + * 授权角色给某个用户 + * + * @param sysUserRequest 授权参数 + * @author fengshuonan + * @date 2020/11/21 14:43 + */ + void grantRole(SysUserRequest sysUserRequest); + + /** + * 授权组织机构数据范围给某个用户 + * + * @param sysUserRequest 授权参数 + * @author fengshuonan + * @date 2020/11/21 14:48 + */ + void grantData(SysUserRequest sysUserRequest); + + /** + * 删除系统用户 + * + * @param sysUserRequest 删除参数 + * @author fengshuonan + * @date 2020/11/21 14:54 + */ + void delete(SysUserRequest sysUserRequest); + + /** + * 查看用户详情 + * + * @param sysUserRequest 查看参数 + * @return 用户详情结果 + * @author luojie + * @date 2020/11/6 13:46 + */ + SysUserResponse detail(SysUserRequest sysUserRequest); + + /** + * 查询系统用户 + * + * @param sysUserRequest 查询参数 + * searchValue 关键字 + * organizationId 机构id + * statusFlag 用户状态 + * @return 查询分页结果 + * @author fengshuonan + * @date 2020/11/21 15:24 + */ + PageResult page(SysUserRequest sysUserRequest); + + /** + * 获取用户所有角色 + * + * @param sysUserRequest 查询参数 + * @return 角色id集合 + * @author luojie + * @date 2020/11/6 13:47 + */ + List getUserRoles(SysUserRequest sysUserRequest); + + /** + * 用户下拉列表选择 + * + * @param sysUserRequest 查询参数 + * @return 用户列表集合 + * @author luojie + * @date 2020/11/6 13:47 + */ + List selector(SysUserRequest sysUserRequest); + + /** + * 导出用户 + * + * @param response httpResponse + * @author luojie + * @date 2020/11/6 13:47 + */ + void export(HttpServletResponse response); + + /** + * 根据账号获取用户 + * + * @param account 账号 + * @return 用户 + * @author luojie + * @date 2020/11/6 15:09 + */ + SysUser getUserByCount(String account); + +} diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/impl/SysUserDataScopeServiceImpl.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/impl/SysUserDataScopeServiceImpl.java new file mode 100644 index 000000000..68380eb11 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/impl/SysUserDataScopeServiceImpl.java @@ -0,0 +1,65 @@ +package cn.stylefeng.roses.kernel.system.modular.user.service.impl; + +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUserDataScope; +import cn.stylefeng.roses.kernel.system.modular.user.mapper.SysUserDataScopeMapper; +import cn.stylefeng.roses.kernel.system.modular.user.pojo.request.SysUserRequest; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.system.modular.user.service.SysUserDataScopeService; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 系统用户数据范围service接口实现类 + * + * @author luojie + * @date 2020/11/6 10:28 + */ +@Service +public class SysUserDataScopeServiceImpl extends ServiceImpl implements SysUserDataScopeService { + + @Override + public void grantData(SysUserRequest sysUserRequest) { + + // 获取用户id + Long userId = sysUserRequest.getId(); + + // 删除该用户的数据范围集合,sys_user_data_scope表中 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserDataScope::getUserId, userId); + this.remove(queryWrapper); + + List orgIdList = sysUserRequest.getGrantOrgIdList(); + + // 授权组织机构数据范围给用户 + ArrayList sysUserDataScopes = new ArrayList<>(); + for (Long orgId : orgIdList) { + SysUserDataScope sysUserDataScope = new SysUserDataScope(); + sysUserDataScope.setUserId(userId); + sysUserDataScope.setOrganizationId(orgId); + sysUserDataScopes.add(sysUserDataScope); + } + this.saveBatch(sysUserDataScopes); + } + + @Override + public List getUserDataScopeIdList(Long uerId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserDataScope::getUserId, uerId); + queryWrapper.select(SysUserDataScope::getOrganizationId); + + List list = this.list(queryWrapper); + return list.stream().map(SysUserDataScope::getOrganizationId).collect(Collectors.toList()); + } + + @Override + public void deleteUserDataScopeListByUserId(Long userId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserDataScope::getUserId, userId); + + this.remove(queryWrapper); + } +} diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/impl/SysUserRoleServiceImpl.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/impl/SysUserRoleServiceImpl.java new file mode 100644 index 000000000..eca71a8f5 --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/impl/SysUserRoleServiceImpl.java @@ -0,0 +1,65 @@ +package cn.stylefeng.roses.kernel.system.modular.user.service.impl; + +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUserRole; +import cn.stylefeng.roses.kernel.system.modular.user.mapper.SysUserRoleMapper; +import cn.stylefeng.roses.kernel.system.modular.user.pojo.request.SysUserRequest; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.system.DataScopeApi; +import cn.stylefeng.roses.kernel.system.RoleServiceApi; +import cn.stylefeng.roses.kernel.system.modular.user.service.SysUserRoleService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; + +/** + * 系统用户角色service接口实现类 + * + * @author luojie + * @date 2020/11/6 10:28 + */ +@Service +public class SysUserRoleServiceImpl extends ServiceImpl implements SysUserRoleService { + + @Resource + private RoleServiceApi roleServiceApi; + + @Resource + private DataScopeApi dataScopeApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public void grantRole(SysUserRequest sysUserRequest) { + + // 获取用户id + Long userId = sysUserRequest.getId(); + + // 删除该用户的所有角色 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserRole::getUserId, userId); + this.remove(queryWrapper); + + // 为该用户授权角色 + List roleIdList = sysUserRequest.getGrantRoleIdList(); + + ArrayList sysUserRoles = new ArrayList<>(); + for (Long roleId : roleIdList) { + SysUserRole sysUserRole = new SysUserRole(); + sysUserRole.setUserId(userId); + sysUserRole.setRoleId(roleId); + sysUserRoles.add(sysUserRole); + } + this.saveBatch(sysUserRoles); + } + + @Override + public void deleteUserRoleListByUserId(Long userId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserRole::getUserId, userId); + + this.remove(queryWrapper); + } +} \ No newline at end of file diff --git a/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/impl/SysUserServiceImpl.java b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/impl/SysUserServiceImpl.java new file mode 100644 index 000000000..3ecce20ee --- /dev/null +++ b/kernel-s-system/system-business-user/src/main/java/cn/stylefeng/roses/kernel/system/modular/user/service/impl/SysUserServiceImpl.java @@ -0,0 +1,475 @@ +package cn.stylefeng.roses.kernel.system.modular.user.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.BCrypt; +import cn.stylefeng.roses.kernel.system.enums.UserStatusEnum; +import cn.stylefeng.roses.kernel.system.exception.SystemModularException; +import cn.stylefeng.roses.kernel.system.exception.enums.DataScopeExceptionEnum; +import cn.stylefeng.roses.kernel.system.exception.enums.SysUserExceptionEnum; +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUser; +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUserDataScope; +import cn.stylefeng.roses.kernel.system.modular.user.entity.SysUserRole; +import cn.stylefeng.roses.kernel.system.modular.user.mapper.SysUserMapper; +import cn.stylefeng.roses.kernel.system.modular.user.pojo.request.SysUserRequest; +import cn.stylefeng.roses.kernel.system.modular.user.pojo.response.SysUserResponse; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysEmployeeRequest; +import cn.stylefeng.roses.kernel.system.pojo.organization.SysEmployeeResponse; +import cn.stylefeng.roses.kernel.system.pojo.user.UserLoginInfoDTO; +import cn.stylefeng.roses.kernel.system.util.DataScopeUtil; +import com.alibaba.excel.support.ExcelTypeEnum; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import cn.stylefeng.roses.kernel.auth.api.expander.AuthConfigExpander; +import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser; +import cn.stylefeng.roses.kernel.db.api.factory.PageFactory; +import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory; +import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult; +import cn.stylefeng.roses.kernel.office.api.OfficeExcelApi; +import cn.stylefeng.roses.kernel.office.api.pojo.report.ExcelExportParam; +import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum; +import cn.stylefeng.roses.kernel.rule.pojo.dict.SimpleDict; +import cn.stylefeng.roses.kernel.system.DataScopeApi; +import cn.stylefeng.roses.kernel.system.SysEmployeeApi; +import cn.stylefeng.roses.kernel.system.UserServiceApi; +import cn.stylefeng.roses.kernel.system.modular.user.factory.LoginUserFactory; +import cn.stylefeng.roses.kernel.system.modular.user.factory.SysUserFactory; +import cn.stylefeng.roses.kernel.system.modular.user.service.SysUserDataScopeService; +import cn.stylefeng.roses.kernel.system.modular.user.service.SysUserRoleService; +import cn.stylefeng.roses.kernel.system.modular.user.service.SysUserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 用户服务实现类 + * + * @author fengshuonan + * @date 2020/11/21 15:04 + */ +@Slf4j +@Service +public class SysUserServiceImpl extends ServiceImpl implements SysUserService, UserServiceApi { + + @Resource + private SysEmployeeApi sysEmployeeApi; + + @Resource + private SysUserRoleService sysUserRoleService; + + @Resource + private SysUserDataScopeService sysUserDataScopeService; + + @Resource + private OfficeExcelApi officeExcelApi; + + @Resource + private DataScopeApi dataScopeApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public void add(SysUserRequest sysUserRequest) { + + // 获取被添加用户的主组织机构id + Long organizationId = sysUserRequest.getUserMainEmployee().getOrganizationId(); + + // 获取用户有无该企业的数据权限 + if (DataScopeUtil.validateDataScopeByOrganizationId(organizationId)) { + String userTip = StrUtil.format(DataScopeExceptionEnum.DATA_SCOPE_ERROR.getUserTip(), DataScopeUtil.getDataScopeTip()); + throw new SystemModularException(DataScopeExceptionEnum.DATA_SCOPE_ERROR, userTip); + } + + // 请求bean转为实体,填充一些基本属性 + SysUser sysUser = new SysUser(); + BeanUtil.copyProperties(sysUserRequest, sysUser); + SysUserFactory.fillAddSysUser(sysUser); + + // 保存用户 + this.save(sysUser); + + Long sysUserId = sysUser.getId(); + + // 增加员工信息 + List sysEmployeeRequest = sysUserRequest.getSysEmployeeRequest(); + for (SysEmployeeRequest employeeRequest : sysEmployeeRequest) { + employeeRequest.setUserId(sysUserId); + } + + // 更新用户员工信息 + sysEmployeeApi.updateEmployee(sysUserId, ListUtil.toList(sysEmployeeRequest)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void edit(SysUserRequest sysUserRequest) { + + // 获取被添加用户的主组织机构id + Long organizationId = sysUserRequest.getUserMainEmployee().getOrganizationId(); + + // 获取用户有无该企业的数据权限 + if (DataScopeUtil.validateDataScopeByOrganizationId(organizationId)) { + String userTip = StrUtil.format(DataScopeExceptionEnum.DATA_SCOPE_ERROR.getUserTip(), DataScopeUtil.getDataScopeTip()); + throw new SystemModularException(DataScopeExceptionEnum.DATA_SCOPE_ERROR, userTip); + } + + // 转化为实体 + SysUser sysUser = this.querySysUser(sysUserRequest); + BeanUtil.copyProperties(sysUserRequest, sysUser); + + // 填充基础参数 + SysUserFactory.fillEditSysUser(sysUser); + this.updateById(sysUser); + + Long sysUserId = sysUser.getId(); + + // 编辑员工信息 + List sysEmployeeRequest = sysUserRequest.getSysEmployeeRequest(); + for (SysEmployeeRequest employeeRequest : sysEmployeeRequest) { + employeeRequest.setUserId(sysUserId); + } + + // 更新用户员工信息 + sysEmployeeApi.updateEmployee(sysUserId, ListUtil.toList(sysEmployeeRequest)); + } + + @Override + public void updateInfo(SysUserRequest sysUserRequest) { + SysUser sysUser = this.querySysUser(sysUserRequest); + + // 填充更新用户的信息 + SysUserFactory.fillUpdateInfo(sysUserRequest, sysUser); + + this.updateById(sysUser); + } + + @Override + public void changeStatus(SysUserRequest sysUserRequest) { + + // 校验状态在不在枚举值里 + Integer statusFlag = sysUserRequest.getStatusFlag(); + UserStatusEnum.validateUserStatus(statusFlag); + + SysUser sysUser = this.querySysUser(sysUserRequest); + + // 不能修改超级管理员状态 + if (YesOrNotEnum.Y.getCode().equals(sysUser.getSuperAdminFlag())) { + throw new SystemModularException(SysUserExceptionEnum.USER_CAN_NOT_UPDATE_ADMIN); + } + + Long id = sysUser.getId(); + + // 更新枚举,更新只能更新未删除状态的 + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(SysUser::getId, id) + .and(i -> i.ne(SysUser::getDelFlag, YesOrNotEnum.Y.getCode())) + .set(SysUser::getStatusFlag, statusFlag); + + boolean update = this.update(updateWrapper); + if (!update) { + log.error(SysUserExceptionEnum.UPDATE_USER_STATUS_ERROR.getUserTip()); + throw new SystemModularException(SysUserExceptionEnum.UPDATE_USER_STATUS_ERROR); + } + } + + @Override + public void updatePwd(SysUserRequest sysUserRequest) { + SysUser sysUser = this.querySysUser(sysUserRequest); + + // 新密码与原密码相同 + if (sysUserRequest.getNewPassword().equals(sysUserRequest.getPassword())) { + throw new SystemModularException(SysUserExceptionEnum.USER_PWD_REPEAT); + } + + // 原密码错误 + if (!BCrypt.checkpw(sysUserRequest.getPassword(), sysUser.getPassword())) { + throw new SystemModularException(SysUserExceptionEnum.USER_PWD_ERROR); + } + + sysUser.setPassword(BCrypt.hashpw(sysUserRequest.getNewPassword(), BCrypt.gensalt())); + this.updateById(sysUser); + } + + @Override + public void resetPwd(SysUserRequest sysUserRequest) { + SysUser sysUser = this.querySysUser(sysUserRequest); + + // 获取系统配置的默认密码 + String password = AuthConfigExpander.getDefaultPassWord(); + sysUser.setPassword(BCrypt.hashpw(password, BCrypt.gensalt())); + + this.updateById(sysUser); + } + + @Override + public void updateAvatar(SysUserRequest sysUserRequest) { + SysUser sysUser = this.querySysUser(sysUserRequest); + sysUser.setAvatar(sysUserRequest.getAvatar()); + this.updateById(sysUser); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void grantRole(SysUserRequest sysUserRequest) { + SysUser sysUser = this.querySysUser(sysUserRequest); + + // 获取要授权角色的用户的所属机构 + SysEmployeeResponse sysEmployeeResponse = sysEmployeeApi.getUserMainEmployee(sysUser.getId()); + Long organizationId = sysEmployeeResponse.getOrganizationId(); + + // 判断当前用户有无该用户的权限 + DataScopeUtil.validateDataScopeByOrganizationId(organizationId); + + // 给用户授权角色 + sysUserRoleService.grantRole(sysUserRequest); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void grantData(SysUserRequest sysUserRequest) { + SysUser sysUser = this.querySysUser(sysUserRequest); + + // 获取被授权用户的所属机构 + SysEmployeeResponse sysEmployeeResponse = sysEmployeeApi.getUserMainEmployee(sysUser.getId()); + Long organizationId = sysEmployeeResponse.getOrganizationId(); + + // 判断当前用户有无该用户的权限 + DataScopeUtil.validateDataScopeByOrganizationId(organizationId); + + sysUserDataScopeService.grantData(sysUserRequest); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(SysUserRequest sysUserRequest) { + SysUser sysUser = this.querySysUser(sysUserRequest); + + // 不能删除超级管理员 + if (YesOrNotEnum.Y.getCode().equals(sysUser.getSuperAdminFlag())) { + throw new SystemModularException(SysUserExceptionEnum.USER_CAN_NOT_DELETE_ADMIN); + } + + // 获取被授权用户的所属机构 + SysEmployeeResponse sysEmployeeResponse = sysEmployeeApi.getUserMainEmployee(sysUser.getId()); + Long organizationId = sysEmployeeResponse.getOrganizationId(); + + // 判断当前用户有无该用户的权限 + DataScopeUtil.validateDataScopeByOrganizationId(organizationId); + + // 逻辑删除,设置标识位Y + sysUser.setDelFlag(YesOrNotEnum.Y.getCode()); + this.updateById(sysUser); + + Long userId = sysUser.getId(); + + // 删除该用户对应的员工表信息 + sysEmployeeApi.deleteEmployeeByUserId(userId); + + // 删除该用户对应的用户-角色表关联信息 + sysUserRoleService.deleteUserRoleListByUserId(userId); + + // 删除该用户对应的用户-数据范围表关联信息 + sysUserDataScopeService.deleteUserDataScopeListByUserId(userId); + } + + @Override + public SysUserResponse detail(SysUserRequest sysUserRequest) { + SysUserResponse sysUserResponse = new SysUserResponse(); + + // 获取用户基本信息 + SysUser sysUser = this.querySysUser(sysUserRequest); + BeanUtil.copyProperties(sysUser, sysUserRequest); + + // 获取对应员工信息 + List sysEmployeeResponse = sysEmployeeApi.getUserAllEmployee(sysUser.getId()); + sysUserResponse.setSysEmployeeResponse(sysEmployeeResponse); + + return sysUserResponse; + } + + @Override + public PageResult page(SysUserRequest sysUserRequest) { + + Page userPage = + this.baseMapper.findUserPage(PageFactory.defaultPage(), sysUserRequest); + + return PageResultFactory.createPageResult(userPage); + } + + @Override + public List getUserRoles(SysUserRequest sysUserRequest) { + return this.getUserRoleIdList(sysUserRequest.getId()); + } + + @Override + public List selector(SysUserRequest sysUserRequest) { + + LambdaQueryWrapper wrapper = createWrapper(sysUserRequest); + + // 排除超级管理员 + wrapper.ne(SysUser::getSuperAdminFlag, YesOrNotEnum.Y.getCode()); + + // 只查询id和name + wrapper.select(SysUser::getName, SysUser::getId); + List list = this.list(wrapper); + + ArrayList results = new ArrayList<>(); + for (SysUser sysUser : list) { + SimpleDict simpleDict = new SimpleDict(); + simpleDict.setId(sysUser.getId()); + simpleDict.setName(sysUser.getName()); + results.add(simpleDict); + } + + return results; + } + + @Override + public void export(HttpServletResponse response) { + ExcelExportParam excelExportParam = new ExcelExportParam(); + List sysUserList = this.list(); + + excelExportParam.setClazz(SysUser.class); + excelExportParam.setDataList(sysUserList); + excelExportParam.setExcelTypeEnum(ExcelTypeEnum.XLS); + excelExportParam.setFileName("系统用户导出"); + excelExportParam.setResponse(response); + + officeExcelApi.easyExportDownload(excelExportParam); + } + + @Override + public SysUser getUserByCount(String account) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUser::getAccount, account); + queryWrapper.ne(SysUser::getDelFlag, YesOrNotEnum.Y.getCode()); + return this.getOne(queryWrapper); + } + + @Override + public UserLoginInfoDTO getUserLoginInfo(String account) { + UserLoginInfoDTO userLoginInfoDTO = new UserLoginInfoDTO(); + + // 根据账号获取系统用户表中的信息 + SysUser sysUser = getUserByCount(account); + LoginUser loginUser = LoginUserFactory.createLoginUser(sysUser); + + // 获取用户加密的密码,用于登录校验 + userLoginInfoDTO.setUserPasswordHexed(sysUser.getPassword()); + + // 填充用户状态 + userLoginInfoDTO.setUserStatus(sysUser.getStatusFlag()); + + // 设置用户登录详情信息 + userLoginInfoDTO.setLoginUser(loginUser); + + return userLoginInfoDTO; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void updateUserLoginInfo(Long userId, Date date, String ip) { + + // 根据用户id获取用户信息实体 + LambdaQueryWrapper sysUserLambdaQueryWrapper = new LambdaQueryWrapper<>(); + sysUserLambdaQueryWrapper.eq(SysUser::getId, userId).eq(SysUser::getDelFlag, YesOrNotEnum.N.getCode()); + SysUser sysUser = this.getOne(sysUserLambdaQueryWrapper); + + if (sysUser != null) { + // 更新用户登录信息 + SysUser newSysUser = new SysUser(); + newSysUser.setId(sysUser.getId()); + newSysUser.setLastLoginIp(ip); + newSysUser.setLastLoginTime(date); + this.updateById(newSysUser); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteUserDataScopeListByOrgIdList(Set organizationIds) { + if (organizationIds != null && organizationIds.size() > 0) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysUserDataScope::getOrganizationId, organizationIds); + sysUserDataScopeService.remove(queryWrapper); + } + } + + @Override + public List getUserRoleIdList(Long userId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserRole::getUserId, userId); + queryWrapper.select(SysUserRole::getRoleId); + + List list = sysUserRoleService.list(queryWrapper); + return list.stream().map(SysUserRole::getRoleId).collect(Collectors.toList()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void deleteUserRoleListByRoleId(Long roleId) { + if (roleId != null) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserRole::getRoleId, roleId); + sysUserRoleService.remove(queryWrapper); + } + } + + @Override + public List getUserBindDataScope(Long userId) { + return sysUserDataScopeService.getUserDataScopeIdList(userId); + } + + /** + * 获取系统用户 + * + * @author fengshuonan + * @date 2020/3/26 9:54 + */ + private SysUser querySysUser(SysUserRequest sysUserRequest) { + SysUser sysUser = this.getById(sysUserRequest.getId()); + if (ObjectUtil.isNull(sysUser)) { + throw new SystemModularException(SysUserExceptionEnum.USER_NOT_EXIST); + } + return sysUser; + } + + /** + * 创建查询用户的wrapper + * + * @author fengshuonan + * @date 2020/11/6 10:16 + */ + private LambdaQueryWrapper createWrapper(SysUserRequest sysUserRequest) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysUserRequest)) { + + // 组装账号的查询条件 + if (ObjectUtil.isNotEmpty(sysUserRequest.getAccount())) { + queryWrapper.like(SysUser::getAccount, sysUserRequest.getAccount()); + } + + // 组装用户姓名的查询条件 + if (ObjectUtil.isNotEmpty(sysUserRequest.getName())) { + queryWrapper.eq(SysUser::getName, sysUserRequest.getName()); + } + } + + // 查询未删除状态的 + queryWrapper.eq(SysUser::getDelFlag, YesOrNotEnum.N.getCode()); + + return queryWrapper; + } + +} diff --git a/kernel-s-system/system-spring-boot-starter/README.md b/kernel-s-system/system-spring-boot-starter/README.md new file mode 100644 index 000000000..f7c67cfa3 --- /dev/null +++ b/kernel-s-system/system-spring-boot-starter/README.md @@ -0,0 +1 @@ +system业务的spring boot自动加载模块 \ No newline at end of file diff --git a/kernel-s-system/system-spring-boot-starter/pom.xml b/kernel-s-system/system-spring-boot-starter/pom.xml new file mode 100644 index 000000000..5ee383d7b --- /dev/null +++ b/kernel-s-system/system-spring-boot-starter/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + + cn.stylefeng.roses + kernel-s-system + 1.0.0 + ../pom.xml + + + system-spring-boot-starter + + jar + + + + + + cn.stylefeng.roses + system-business-app + 1.0.0 + + + + + cn.stylefeng.roses + system-business-menu + 1.0.0 + + + + + cn.stylefeng.roses + system-business-organization + 1.0.0 + + + + + cn.stylefeng.roses + system-business-resource + 1.0.0 + + + + + cn.stylefeng.roses + system-business-role + 1.0.0 + + + + + cn.stylefeng.roses + system-business-user + 1.0.0 + + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + true + + + + compile + + jar + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..37d308712 --- /dev/null +++ b/pom.xml @@ -0,0 +1,275 @@ + + + 4.0.0 + + cn.stylefeng.roses + roses-kernel + 1.0.0 + + pom + + roses-kernel + Roses是开源项目Guns的核心包 + + + org.springframework.boot + spring-boot-starter-parent + 2.3.5.RELEASE + + + + + kernel-a-rule + + + kernel-d-db + + + kernel-d-auth + + + kernel-d-config + + + kernel-d-validator + + + kernel-d-jwt + + + kernel-d-cache + + + kernel-d-email + + + kernel-d-file + + + kernel-d-sms + + + kernel-d-timer + + + kernel-d-log + + + kernel-d-ds-container + + + kernel-d-scanner + + + kernel-d-office + + + kernel-d-pinyin + + + kernel-s-demo + + + kernel-s-dict + + + kernel-s-system + + + + 1.8 + UTF-8 + UTF-8 + 5.4.4 + 1.2.74 + 1.18.14 + 3.4.0 + 8.0.21 + 1.2.1 + 3.0.0 + 0.9.1 + 1.4.7 + 3.8.0 + 5.6.23 + 1.1.3 + 4.4.6 + 4.17.6 + 3.1.57 + 7.9.2 + 1.11.106 + 3.0.10 + 4.5.2 + + + + + + + + cn.hutool + hutool-all + ${hutool.version} + + + + + com.alibaba + fastjson + ${fastjson.version} + + + + + org.projectlombok + lombok + ${lombok.versin} + + + + + com.baomidou + mybatis-plus-boot-starter + ${mp.version} + + + + + mysql + mysql-connector-java + ${mysql-connector-java.version} + + + + + com.alibaba + druid + ${druid.version} + + + + + io.springfox + springfox-boot-starter + ${swagger.version} + + + + + io.jsonwebtoken + jjwt + ${jwt.version} + + + + + javax.mail + mail + ${java.mail.version} + + + + + com.aliyun.oss + aliyun-sdk-oss + ${aliyun.oss.version} + + + + + com.qcloud + cos_api + ${qcloud.cos.version} + + + commons-logging + commons-logging + ${qcloud.commons.logging.version} + + + + + com.aliyun + aliyun-java-sdk-core + ${aliyun.sms.sdk.core} + + + com.aliyun + aliyun-java-sdk-ecs + ${aliyun.sms.sdk.ecs} + + + + + com.tencentcloudapi + tencentcloud-sdk-java + ${qcloud.sms} + + + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + ${elasticsearch.version} + + + org.elasticsearch + elasticsearch + ${elasticsearch.version} + + + org.elasticsearch.client + elasticsearch-rest-client + ${elasticsearch.version} + + + + + com.amazonaws + aws-java-sdk-bom + ${aws.sdk.version} + pom + import + + + io.minio + minio + ${minio.version} + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + + + + + src/main/resources + + + src/main/java + + **/*.xml + + + + + + + + fengshuonan + sn93@qq.com + + + + \ No newline at end of file